网站首页 返回列表 像“草根”一样,紧贴着地面,低调的存在,冬去春来,枯荣无恙。 Golang学习笔记之初识并发特性(上) 23-09-20 11:05:29 字节波 329 初始Go语言的并发特性,学习本节会对Go语言的并发形成一个清晰的概念,Go作为并发式语言,原生支持并发,具体来说它是通过Go协程(Goroutine)和信道(Channel)来处理并发的。 ### 并发是什么 并发是指立即处理多个任务的能力。 - 举个简单的例子说明: 我们可以想象一个人正在跑步。假如在他晨跑时,鞋带突然松了。于是他停下来,系一下鞋带,接下来继续跑。这个例子就是典型的并发。这个人能够一下搞定跑步和系鞋带两件事,即立即处理多个任务。 ### 并发与并行的区别 上例中可以看到一个人立即处理了多个任务,但并非同时完成的,这与并行是完全不同的,并行是指同时处理多个任务。 - 同样举个跑步的例子说明: 假如这个人在慢跑时,还在用他的iPod听着音乐。在这里,他是在跑步的同时听音乐,也就是同时处理多个任务。这称之为并行。 ### 再来从技术角度来看并发与并行 假如我们正在编写一个web浏览器。这个web浏览器有各种组件。其中两个分别是web页面的渲染区和从网上下载文件的下载器。假设我们已经构建好了浏览器代码,各个组件也都可以相互独立地运行(通过像Java里的线程,或者通过即将介绍的Go语言中的Go协程来实现)。当浏览器在单核处理器中运行时,处理器会在浏览器的两个组件间进行上下文切换。它可能在一段时间内下载文件,转而又对用户请求的web页面进行渲染。这就是并发。并发的进程从不同的时间点开始,分别交替运行。在这里,就是在不同的时间点开始进行下载和渲染,并相互交替运行的。 如果该浏览器在一个多核处理器上运行,此时下载文件的组件和渲染HTML的组件可能会在不同的核上同时运行。这称之为并行。 - 如图所示:  并行不一定会加快运行速度,因为并行运行的组件之间可能需要相互通信。在我们浏览器的例子里,当文件下载完成后,应当对用户进行提醒,比如弹出一个窗口。于是,在负责下载的组件和负责渲染用户界面的组件之间,就产生了通信。在并发系统上,这种通信开销很小。但在多核的并行系统上,组件间的通信开销就很高了。所以,并行不一定会加快运行速度! ### Go协程(Goroutine)是什么 Go 协程是与其他函数或方法一起并发运行的函数或方法。Go 协程可以看作是轻量级线程。与线程相比,创建一个 Go 协程的成本很小。因此在 Go 应用中,常常会看到有数以千计的 Go 协程并发地运行。 ### Go协程相比于线程的优势 - 相比线程而言,Go协程的成本极低。堆栈大小只有若干kb,并且可以根据应用的需求进行增减。而线程必须指定堆栈的大小,其堆栈是固定不变的。 - Go协程会复用(Multiplex)数量更少的OS线程。即使程序有数以千计的Go协程,也可能只有一个线程。如果该线程中的某一Go协程发生了阻塞(比如说等待用户输入),那么系统会再创建一个OS线程,并把其余Go协程都移动到这个新的OS线程。所有这一切都在运行时进行,作为程序员,我们没有直接面临这些复杂的细节,而是有一个简洁的API来处理并发。 - Go协程使用信道(Channel)来进行通信。信道用于防止多个协程访问共享内存时发生竞态条件(Race Condition)。信道可以看作是 Go协程之间通信的管道。信道(Channel)会在下一章节详细说明。 ### 如何启动一个Go协程 调用函数或者方法时,在前面加上关键字go,可以让一个新的Go协程并发地运行。 创建一个Go协程: ```go package main import ( "fmt" ) func hello() { fmt.Println("Hello world goroutine") } func main() { go hello() fmt.Println("main function") } ``` 在第11行,`go hello()`启动了一个新的Go协程。现在`hello()`函数与`main()`函数会并发地执行。主函数会运行在一个特有的Go协程上,它称为Go主协程(Main Goroutine)。 运行该程序会惊讶发现并没有输出期望的打印内容,只输出了`main function`,这是什么原因呢?想要理解为什么,首先需要理解两个Go协程的主要性质。 - 启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待Go协程执行完毕。在调用Go协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。 - 如果希望运行其他Go协程,Go主协程必须继续运行着。如果Go主协程终止,则程序终止,于是其他Go协程也不会继续运行。 因此,我们刚才运行的Go协程没有运行。在第11行调用了`go hello()`之后,程序控制没有等待`hello`协程结束,立即返回到了代码下一行,打印`main function`。接着由于没有其他可执行的代码,Go主协程终止,于是`hello`协程就没有机会运行了。 我们现在修复这个问题。 ```go package main import ( "fmt" "time" ) func hello() { fmt.Println("Hello world goroutine") } func main() { go hello() time.Sleep(1 * time.Second) fmt.Println("main function") } ``` 我们让Go主协程休眠1秒,再终止之前,调用`go hello()`就有足够的时间来执行了,该程序首先打印`Hello world goroutine`,等待1秒钟之后,接着打印`main function`。这里我们是使用了休眠来等待协程执行完毕。 ### 启动多个 Go 协程 为了更好地理解 Go 协程,我们再编写一个程序,启动多个 Go 协程。 ```go package main import ( "fmt" "time" ) func numbers() { for i := 1; i <= 5; i++ { time.Sleep(250 * time.Millisecond) fmt.Printf("%d ", i) } } func alphabets() { for i := 'a'; i <= 'e'; i++ { time.Sleep(400 * time.Millisecond) fmt.Printf("%c ", i) } } func main() { go numbers() go alphabets() time.Sleep(3000 * time.Millisecond) fmt.Println("main terminated") } ``` 在上面程序中的第21行和第22行,启动了两个Go协程。现在,这两个协程并发地运行。numbers协程首先休眠250微秒,接着打印1,然后再次休眠,打印2,依此类推,一直到打印5结束。alphabete协程同样打印从a到e的字母,并且每次有400微秒的休眠时间。Go主协程启动了numbers和alphabete两个Go协程,休眠了3000微秒后终止程序。 该程序会输出: ```shell 1 a 2 3 b 4 c 5 d e main terminated ``` 程序的运作如下图所示:  第一张蓝色的图表示numbers协程,第二张褐红色的图表示alphabets协程,第三张绿色的图表示Go主协程,而最后一张黑色的图把以上三种协程合并了,表明程序是如何运行的。在每个方框顶部,诸如0ms和250ms这样的字符串表示时间(以微秒为单位)。在每个方框的底部,1、2、3等表示输出。蓝色方框表示:250ms打印出1,500 ms打印出2,依此类推。最后黑色方框的底部的值会是 1 a 2 3 b 4 c 5 d e main terminated,这同样也是整个程序的输出。 利用休眠这种方法只是用于理解Go协程如何工作的技巧,而实际运用中,这种方法并不可取,因此,Go的另一个灵魂特性Channel闪亮登场了。 待续。。。 关键字词[Golang, Goroutine, Channel] 分享到: 上一篇:Golang学习笔记之匿名函数与闭包 下一篇:Golang学习笔记之初识并发特性(下) 如需留言,请 登录,没有账号?请 注册 0 条评论 0 人参与 最新文章 Dapp合约开发指南 ansible学习记录-远程开启exe不能挂起UI界面 leetcode基础算法学习之maxArea leetcode基础算法学习之ReverseInt leetcode基础算法学习之LongestSubstr leetcode基础算法学习之addTwoNumbers leetcode基础算法学习之FindIndex CentOS7安装nginx服务 点击排行 优雅的语言开发优雅的站点 Beego框架第1节——环境与初始 Golang学习笔记之匿名函数与闭包 Golang学习笔记之interface Dapp合约开发指南 最新评论 字节波 官方 1年前 你好,可以,麻烦你的站点做好友链 字节波 官方 1年前 欢迎各界人士评论留言,注意要遵守法律法规,祝每一位... 友情链接 BYTE STUDIO 字节波 ByteWave 360导航 360安全浏览器
0 条评论 0 人参与