第八章(并发)[一]
并发和并行的区别
- 并发:逻辑上具备同时处理多个任务的能力(片面理解:多个进程来回切换执行,用户感知到多个程序同时执行一样)
- 并行:物理上在同一时刻执行多个并发任务(片面理解:多核cpu分别同时执行多个线程,真正的同时执行)
并行是并发设计的理想执行模式
-
多线程或多进程是并行的基本条件,但单线程也可用协程协程做到并发。
-
协程在单个线程上通过主动切换来实现多任务并发
-
goroutine
更像是多线程和协程的综合体:运行时创建多个线程来执行并发任务,且任务单元可被调度到其它线程并行执行。能最大线限度提升执行效率,发挥多核处理能力 -
go
关键字并非执行并发操作,而是创建一个并发任务单元。新建任务被放置在系统队列中,等待调度器安排合适的系统线程去获取执行权 -
每个任务单元除保存函数指针,调用参数外,还会分配执行所需的栈内存空间。相比系统默认
MB
级别的线程栈,goroutine
自定义栈初始仅须2KB,所以才能创建成千上万的并发任务。 -
自定义栈采取按需分配策略,在需要时进行扩容,最大能到GB规模(在不同版本中,自定义栈大小略有不同)
与
defer
一样,goroutine
也会立即计算并复制参数。代码如下:
package main
import "time"
var c int
func counter() int {
c++
return c
}
func main() {
a := 100
go func(x, y int) {
time.Sleep(time.Second)
println("goroutine:", x, y)
}(a, counter()) //立即计算并复制参数 100,1
a += 100
println("main:", a, counter()) //200,2
time.Sleep(time.Second * 3)
}
Wait
- 进程退出时不会等待并发任务结束,可用
channel
阻塞,然后发出退出信号
func main() {
exit := make(chan struct{}) //使用空结构体作为通道元素,用于事件通知
go func() {
time.Sleep(time.Second)
println("goroutine done")
close(exit) //关闭通道
}()
println("main ...")
<-exit //读取通道,没有数据或通道未关闭时一直阻塞
println("main exit")
}
- 如若等待多个任务结束,可使用
sync.WaitGroup
通过设定计数器,让每个goroutine
在退出前递增,直至归零时解除阻塞。
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1) //尽管WaitGroup.Add实现了原子操作,但建议在goroutine外累加计数器,以免Add尚未执行,Wait已经退出
go func(id int) {
defer wg.Done()
time.Sleep(time.Second)
println("goroutine", id, "done.")
}(i)
}
println("main...")
wg.Wait()
println("main exit.")
/*
main...
goroutine 5 done.
goroutine 7 done.
goroutine 0 done.
goroutine 9 done.
goroutine 4 done.
goroutine 2 done.
goroutine 3 done.
goroutine 6 done.
goroutine 1 done.
goroutine 8 done.
main exit.
*/
}
- 也可多处使用Wait阻塞,它们都能接收到通知(在1.17.3测试:多处goroutine中使用wait并不会阻塞,也不会接收到通知,)
Goexit
- Goexit立即终止当前任务,运行时确保所有注册延迟调用被执行。该函数不会影响其它并发任务
func main() {
exit := make(chan struct{})
go func() {
defer close(exit)
defer println("a")
func() {
defer func() {
println("b", recover() == nil)
}()
func() {
println("c")
runtime.Goexit() //立即终止当前goroutine,不会终止已注册的延迟调用函数
println("c done.") //不会执行
}()
println("b done.") //不会执行
}()
println("a done.") //不会执行
}()
<-exit
println("main exit.")
}
- 在main.main里调用Goexit,会等其它goroutine结束后让进程直接崩溃
func main() {
for i := 0; i < 5; i++ {
go func(x int) {
println("goroutine:", x)
}(i)
}
runtime.Goexit() //等待其它goroutine结束后再终止进程
println("main exit")
/*goroutine: 0
goroutine: 1
goroutine: 3
goroutine: 2
goroutine: 4
fatal error: no goroutines (main called runtime.Goexit) - deadlock!*/
}
os.Exit
会立即终止进程,不会执行延迟调用