goroutine
# goroutine
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
for {
// io 操作 会交出控制权
fmt.Printf("hello from"+
"goroutine + %d\n", i)
}
}(i)
}
time.Sleep(time.Millisecond)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
协程:Coroutine
,是一个轻量级的”线程“。
- 非抢占式多任务处理,由协程主动交出控制权
- 编译器、解释器、虚拟机层面的多任务,具体实现上会由调度器管理
- 多个协程可能在一个或多个线程上运行
我们如果把这个i
不传递参数会怎么样?
func main() {
var a [10]int
for i := 0; i < 10; i++ {
go func() {
for {
a[i]++
// 手动交出控制权
runtime.Goexit()
}
}()
}
time.Sleep(time.Millisecond)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
这样就会报错,会提示超出边界的panic
,这是为什么呢?
因为使用的是同一个
i
,当main
线程执行完之后,i
最后的值会变成 10,如果此时a[10]++
,就会报超出边界的panic
。
我们可以通过命令行参数来进行查看对比:
➜ go run -race goroutine.go
==================
WARNING: DATA RACE
Read at 0x00c0000c4018 by goroutine 7:
main.main.func1()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:14 +0x70
Previous write at 0x00c0000c4018 by main goroutine:
main.main()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:11 +0xc6
Goroutine 7 (running) created at:
main.main()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:12 +0xa4
==================
==================
WARNING: DATA RACE
Read at 0x00c0000c8010 by goroutine 7:
main.main.func1()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:14 +0x94
Previous write at 0x00c0000c8010 by goroutine 8:
main.main.func1()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:14 +0x4b
Goroutine 7 (running) created at:
main.main()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:12 +0xa4
Goroutine 8 (finished) created at:
main.main()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:12 +0xa4
==================
panic: runtime error: index out of range [10] with length 10
goroutine 28 [running]:
main.main.func1()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:14 +0xde
created by main.main
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:12 +0xa5
exit status 2
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
30
31
32
33
34
35
36
37
38
39
40
41
42
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
30
31
32
33
34
35
36
37
38
39
40
41
42
我们加上传递参数的之后再次运行:
➜ go run -race goroutine.go
==================
WARNING: DATA RACE
Read at 0x00c0000c8000 by main goroutine:
runtime.racereadrange()
<autogenerated>:1 +0x1b
Previous write at 0x00c0000c8000 by goroutine 7:
main.main.func1()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:14 +0x64
main.main·dwrap·1()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:18 +0x47
Goroutine 7 (finished) created at:
main.main()
/Users/GolangProjects/src/learngo/goroutine/goroutine.go:12 +0x7d
==================
[1 1 1 1 1 1 1 1 1 1]
Found 1 data race(s)
exit status 66
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这个就代表,一个在读(
fmt.Println(a)
),一个在写(a[i]++
),这个被它检查出来了,这个需要加上channel
来进行解决,但是我们这里,只需要看下打印的结果就行了。
# 协程 Corouting
- 子程序是协程的一个特例
普通函数,在一个线程里,有一个
main
函数,它调用了doWork
,等到doWork
做完了,控制权才会交给main
函数,才会继续去做其他的事情。
main
和doWork
之间是双向的,并且有通道,且控制权也是双向流通的,表示大家可以各做各的事情,他们可能运行在同一个线程里,可能不在,最终看调度器。
# 其他语言的协程
- C++:
Boost.Coroutine
- Java:不支持
- python 的协程:
- 使用
yield
关键字实现协程 python3.5
加入了async def
对协程原生支持
- 使用
# go 语言的协程
- 任何函数只需加上
go
就能送给调度器运行 - 不需要在定义时区分是否是异步函数,这个是用来和
python
区分,python
需要加上async
- 调度器在合适的点进行切换
- 使用
-race
来检测数据访问的冲突
# goroutine 可能切换的点
I/O
、select
channel
- 等待锁
- 函数调用(有时)
runtime.Gosched()
- 上面只是参考,不能保证切换,不能保证在其他的地方不切换
编辑 (opens new window)
上次更新: 2022/03/27, 20:31:10