Goroutines
第一个小例子
主函数返回时,所有的goroutine都会被直接打断,程序退出。
除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并让被请求的goroutine自行结束执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package main
import (
"fmt"
"time"
)
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func T1() {
go T2()
fmt.Println("t1")
}
func T2() {
fmt.Println("t2")
}
func Test1(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
T1()
}()
wg.Wait()
fmt.Println("t3")
}
|
资源共享并发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func TestCounterThreadSafe2(t *testing.T) {
var mut sync.Mutex
var wg sync.WaitGroup
counter := 0
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
mut.Lock()
defer wg.Done()
defer mut.Unlock()
counter++
}()
}
wg.Wait()
t.Logf("counter = %d", counter)
}
|
参数竞争
匿名函数中的循环变量快照问题。上面这个单独的变量f是被所有的匿名函数值所共享,且会被连续的循环迭代所更新的。当新的goroutine开始执行字面函数时,for循环可能已经更新了f并且开始了另一轮的迭代或者(更有可能的)已经结束了整个循环,所以当这些goroutine开始读取f的值时,它们所看到的值已经是slice的最后一个元素了。显式地添加这个参数,我们能够确保使用的f是当go语句执行时的“当前”那个f。
1
2
3
4
5
6
|
for _, f := range filenames {
go func() {
thumbnail.ImageFile(f) // NOTE: incorrect!
// ...
}()
}
|
正确的做法
1
2
3
4
5
6
|
for _, f := range filenames {
go func(f string) {
thumbnail.ImageFile(f) // NOTE: incorrect!
// ...
}(f)
}
|
GMP 调度器
- goroutine 协程
- machine 是操作系统的主线程, 物理线程
- processor 处理器抽象,负责衔接 M 和 G 的调度上下文,将等待的 G 和 M 对接。P 决定了并行任务的数量,可通过 runtine.GOMAXPROCS 来设定,默认设置是可用核心数。
调度流程
go func()
创建
- 入 局部队列
- 局部已入全局队列
M
获取 G
- 若
m1
的 P
本地队列为空则中全局获取
- 若为空, 从其它
MP
组合中偷取 G
- 调度
- 执行
- 若发生阻塞
- 创建一个
M
或从 休眠队列取一个
- 接管当前正在阻塞
G
的P
- 时间片超时返回
参考
Go:g0,特殊的 Goroutine