简述
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
go 函数名( 参数列表 )
例如:
go eat(x, y)
并发与并行
并行是让不同的代码片段同时在不同的物理处 理器上执行。并行的关键是同时做很多事情,
并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。
调度器与逻辑处理器
操作系统会在物理处理器上调度线程来运行,而 Go 语言的运行时会在逻辑处理器上调度 goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。Go语言的运行时默认会为每个可用的物理处理器分配一个逻辑处理器
图6-2 可以看到操作系统线程、逻辑处理器和本地运行队列之间的关系。如果创建一 个 goroutine 并准备运行,这个 goroutine 就会被放到调度器的全局运行队列中。之后,调度器就将这些队列中的 goroutine 分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中。本地运行队列中的 goroutine 会一直等待直到自己被分配的逻辑处理器执行中。本地运行队列中的 goroutine 会一直等待直到自己被分配的逻辑处理器执行。
goroutine 并发
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(1) // 分配一个逻辑处理器给调度器使用
var wg sync.WaitGroup
wg.Add(2) // 计数加 2,表示要等待两个 goroutine
fmt.Println("Start Goroutines")
// 声明一个匿名函数,并创建一个 goroutine
go func() {
defer wg.Done() // 在函数退出时调用 Done 来通知 main 函数工作已经完成
// 显示字母表 3 次
for count := 0; count < 3; count++ {
for char := 'a'; char < 'a'+26; char++ {
fmt.Printf("%c ", char)
}
}
}()
// 声明一个匿名函数,并创建一个 goroutine
go func() {
defer wg.Done() // 在函数退出时调用 Done 来通知 main 函数工作已经完成
// 显示字母表 3 次
for count := 0; count < 3; count++ {
for char := 'A'; char < 'A'+26; char++ {
fmt.Printf("%c ", char)
}
}
}()
fmt.Println("Waiting To Finish")
wg.Wait() // 等待 goroutine 结束
fmt.Println("\nTerminating Program")
}
runtime.GOMAXPROCS(1) :这里十分关键,我们只分配一个逻辑处理器给调度器使用,也就是说我们只有一个人而要去干两份活
Start Goroutines
Waiting To Finish
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K
L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V
W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g
h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r
s t u v w x y z
Terminating Program
第一个 goroutine 完成所有显示需要花时间太短了,以至于在调度器切换到第二个 goroutine 之前,就完成了所有任务。这也是为什么会看到先输出了所有的大写字母,之后才输出小写字母。
goroutine 实现并行
我们在上面那个例子进行简单的修改,就可让程序并行执行。
runtime.GOMAXPROCS(2) 分配 2 个逻辑处理器给调度器使用,意思就是找两个人一起来干活
修改后的执行结果:
Start Goroutines
Waiting To Finish
A B C a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f D E F G H I J K L M N
O P Q R S T U V W X Y g h i j k l m n o p q r s t u v w x y z a b c d Z A B C D E F G H I J
K L M N O e f g h i j k l m n o p q r s t u v w x y z P Q R S T U V W X Y Z A B C D E F G H
I J K L M N O P Q R S T U V W X Y Z
Terminating Program
Start Goroutines
Waiting To Finish
A a b c B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q
R S T d e f g U V W X Y Z A B C D E F G H I J K L M N O h i j k l m n o p q r s t u v w x y
z a P Q R S T b c d e f g h i U V W X Y Z j k l m n o p q r s t u v w x y z a b c d e f g h
i j k l m n o p q r s t u v w x y z
Terminating Program
可以看到执行的结果大小写字母是交替出现的,同时每次的执行结果不一定相同!
runtime.GOMAXPROCS()
如果省略了 runtime.GOMAXPROCS(2) 这段代码,那么程序将使用默认值来分配逻辑处理器。在 Go 1.5 及其以后的版本中,如果没有设置 GOMAXPROCS 环境变量或调用 GOMAXPROCS 函数,那么程序会使用运行时可用的所有 CPU 核心。这意味着,默认情况下将使用多个逻辑处理器来执行 goroutine
参考书籍《Go语言实战》