并发
首先 go 是一个实现并发的语言,并非并行
并发 vs 并行
并行是在同一时间内,可以同时执行多个任务;并发则是在一个时间片内只能执行单一任务,并把一段时间切分为多个时间片,不同时间片执行不同任务,在宏观上看像是在一段时间内同时执行了多个任务。如下图:
并行不会总是导致更快的执行时间。这是因为并行运行的组件可能必须相互通信。在组件在多个内核中并行运行的情况下,此通信开销很高。因此,并行程序并不总是能够缩短执行时间
在golang 中使用 goroutines 和 channel 实现并发。
什么是 Goroutines
Goroutine是与其他函数或方法同时运行的函数或方法。可以认为它是轻量级的线程。与线程相比,创建Goroutine的成本很小。因此,Go应用程序具有成千上万个同时运行的Goroutine。
Goroutine 的优势
与线程相比,goroutines非常轻量。它们的堆栈大小只有几kb,并且堆栈可以根据应用程序的需要进行扩展和收缩,而对于线程,则必须指定堆栈大小并加以固定。
Goroutines在多路复用时会使用更少的OS线程。在具有数千个Goroutine的程序中,可能只有一个线程。如果该线程块中的任何Goroutine说正在等待用户输入,则将创建另一个OS线程,并将其余的Goroutine移至新的OS线程。作为程序员,我们从这些复杂的细节中抽象出来,并为它们提供了干净的API以进行并发工作。
Goroutine使用 channel 进行通信。通过设计通道,可以防止在使用Goroutines访问共享内存时发生争用情况。可以将 channel 视为与Goroutine进行通信的管道。
如何使用 Goroutine
在函数调用时,增加 go
关键字即可
import (
"fmt"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello() // 开启一个新的 goroutine
fmt.Println("main function")
}
main 函数中创建了一个 goroutine,此时执行 main 函数的线程则称为主goroutine(可以理解为父进程)
然而这段程序只输出main function
,这是为什么呢,首先我们需要知道两条 goroutine的特性
- Goroutine 的调用时立刻返回的,并执行下一行代码。而且 Goroutine 的返回值将会被忽略
- 主Goroutine正在运行,其他任何Goroutine才能运行。如果主Goroutine终止,则程序将终止,并且其他Goroutine将不会运行。
在上述代码片中,主goroutine 开启 hello()
后立刻打印 main function
后退出,因此 hello()
也并未执行完。可以在go hello()
后添加一行代码 time.Sleep(1 * time.Second)
等待其完成(这样做只是为了理解 goroutine 机制,实际中需要使用 channel 来实现等待其他 goroutine 的执行)。
多 goroutine
我们使用下面的代码加深一下理解
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")
}
上述代码的执行流程如图所示
图像的第一部分蓝色代表数字Goroutine,栗色的第二部分代表字母Goroutine,绿色的第三部分代表主Goroutine,黑色的最后一部分将上述三个全部合并,并向我们展示了程序的执行时序。
如果喜欢,请关注我的公众号,或者查看我的博客 http://packyzbq.coding.me. 我会不定时的发送我自己的学习记录,大家互相学习交流哈~
有疑问加站长微信联系(非本文作者)