并发编程-协程使用
Salted Fish 1991/6/26 基础
# 1.什么是协程?
协程又称为微线程,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程,一个线程也可以拥有多个协程。
协程是编译器级的,进程和线程是操作系统级的。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。
# 2.Goroutine
Go语言中的协程叫作Goroutine。Goroutine由Go程序运行时(runtime)调度和管理,Go程序会智能地将Goroutine中的任务合理地分配给每个CPU。创建Goroutine的成本很小,每个Goroutine的堆栈只有几kb,且堆栈可以根据应用程序的需要增长和收缩。
# 2.1 Coroutine与Goroutine的区别
Goroutine能并行执行,Coroutine只能顺序执行;Goroutine可在多线程环境产生,Coroutine只能发生在单线程。Coroutine程序需要主动交出控制权,系统才能获得控制权并将控制权交给其他Coroutine。Coroutine的运行机制属于协作式任务处理,应用程序在不使用CPU时,需要主动交出CPU使用权。如果开发者无意间让应用程序长时间占用CPU,操作系统也无能为力,计算机很容易失去响应或者死机。Goroutine属于抢占式任务处理,和现有的多线程和多进程任务处理非常类似。应用程序对CPU的控制最终由操作系统来管理,如果操作系统发现一个应用程序长时间占用CPU,那么用户有权终止这个任务。
# 3.普通函数创建Goroutine
# 3.1 创建语法
使用go关键字创建Goroutine时,被调用的函数往往没有返回值,如果有返回值也会被忽略。如果需要在Goroutine中返回数据,必须使用channel,通过channel把数据从Goroutine中作为返回值传出。
// 创建Goroutine
go funcName()
1
2
2
# 3.2 使用
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
go echoNum("张三")
go echoNum("李四")
go echoNum("王五")
// 睡眠1秒
time.Sleep(1 * time.Second)
fmt.Println("运行结束")
}
// 报数
func echoNum(who string) {
for i := 1; i < 3; i++ {
fmt.Println(who + " "+ strconv.Itoa(i))
}
}
/** 输出
张三 1
张三 2
王五 1
李四 1
李四 2
王五 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
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
# 4. 匿名函数创建Goroutine
# 4.1 创建语法
go func(){
// ...函数体
}()
1
2
3
2
3
# 4.2 使用
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
go func(who string) {
for i := 1; i < 3; i++ {
fmt.Println(who + " "+ strconv.Itoa(i))
}
}("张三")
go func(who string) {
for i := 1; i < 3; i++ {
fmt.Println(who + " "+ strconv.Itoa(i))
}
}("李四")
go func(who string) {
for i := 1; i < 3; i++ {
fmt.Println(who + " "+ strconv.Itoa(i))
}
}("王五")
// 睡眠1秒
time.Sleep(1 * time.Second)
fmt.Println("运行结束")
}
/** 输出
张三 1
张三 2
王五 1
王五 2
李四 1
李四 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
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
多个Goroutine随机调度,打印的结果是数字与字母交叉输出
# 5. 并发运行性能调整
# 5.1 设置运行的 cpu 数
为了充分了利用多 cpu 的优势,在Golang程序中,可以通过runtime.GOMAXPROCS() 函数 设置运行的 cpu 数目
- 使用格式
runtime.GOMAXPROCS( 逻辑CPU)
//逻辑CPU可以通过 runtime.NumCpu()函数获取
1
2
2
这里的逻辑CPU数量可以有如下几种数值:
| 数值 | 含义 | 示例 |
|---|---|---|
| < 1 | 不修改任何数值 | runtime.GOMAXPROCS(0) |
| = 1 | 单核执行 | runtime.GOMAXPROCS(1) |
| > 1 | 多核并发执行 | runtime.GOMAXPROCS(4) 4核并发执行 |
- Go 1.5版本之前,默认使用的是单核心执行。从Go 1.5版本开始,默认执行
runtime.GOMAXPROCS(逻辑CPU数量),让代码并发执行,最大效率地利用CPU。GOMAXPROCS同时也是一个环境变量,在应用程序启动前设置环境变量也可以起到相同的作用。
# 6.注意事项
- 所有
Goroutine在main()函数结束时会一同结束。 - 如果需要在
Goroutine中返回数据,必须使用channel,通过channel把数据从Goroutine中作为返回值传出。 - 启动多个
Goroutine时,会随机调度。