Go中的并发组件
1、Goroutine
Goroutine: go语言程序中最基本的组织单位之一,每个Go程序中至少会有一个main goroutine,在进程开始时自动创建并启动。是一个并发函数,在程序中使用go关键字进行触发
gorountine如何工作?
A、goroutine与Go语言在运行时是深度集成的
B、goroutine没有定义自己的暂停方法或再运行点
C、Go语言再运行时会观察goroutine的运行时行为,再它们阻塞时自动挂起,不阻塞时恢复,使得goroutione在阻塞时成为可抢占的
D、协程和goroutione是隐式并发结构,但并发不是二者的属性
它们是OS线程或绿色线程吗?
Goroutine不是,是更高级别的抽象协程
绿色线程:相对于操作系统线程,操作系统线程是指程序中会真正的映射到操作系统的线程,绿色线程是指程序中不会映射到操作系统中有语言映射平台自身来调度(由语言运行时管理的线程)
协程:非抢占式的简单并发子goroutine(函数, 闭包或方法), 不能被中断,协程由多个点允许暂停或重新进入
Go语言的主机托管机制:M:N调度器
M个绿色线程映射到N个OS线程,goroutine数量如果大于绿色线程时,这时调度程序会处理分布在可用线程上的goroutine,保证正常运行
Go语言的并发模型fork-join模型:
fork:指在程序的任意一点,可以将执行的子分支与其父节点同时运行
join:将来某个时间,这些并发的执行分支将会合并在一起
Goroutine与GC:
用于计算创建一个goroutine消耗的内存
memConsumed := func ()uint64{
runtime.GC()
var s runtime.MemStats
runtime.ReadMemStats(&s)
return s.Sys
}
var c <-chan interface{}
var wg sync.WaitGroup
noop := func(){ wg.Done(); <-c}
const numGoroutines = 1e4
wg.Add(numGoroutines)
before := memConsumed()
for i := numGoroutines; i > 0; i--{
go noop()
}
wg.Wait()
after := memConsumed()
fmt.Printf("%.3fkb", float64(after - before)/numGoroutines/1000)
理论上8GB内存的计算机在不交换空间的情况下可以启动百万量级的goroutine,可能会影响性能的是上下文,因为OS线程的开销是相当大的,相比之下软件的上下文切换就会廉价
OS线程切换上下文:os线程必须保存如:寄存器,查找表和内存映射之类的东西,以便在有限的时间内成功地切换回当前线程
对比OS线程和goroutine之间切换上下文时的相对性能对比:
Linux基准测试
taskset -c 0 perf bench sched pipe -T
结果:
# Running 'sched/pipe' benchmark:
# Executed 1000000 pipe operations between two threads
Total time: 2.276 [sec]
2.276609 usecs/op
439249 ops/sec
go中的测试
func BenchmarkContestSwitch(b *testing.B){
var wg sync.WaitGroup
begin := make(chan struct{})
c := make(chan struct{})
var token struct{}
sender := func(){
defer wg.Done()
<- begin
for i := 0; i < b.N; i++{
c <- token
}
}
receiver := func(){
defer wg.Done()
<-begin
for i := 0; i < b.N; i++{
<-c
}
}
wg.Add(2)
go sender()
go receiver()
b.StartTimer()
close(begin)
wg.Wait()
}
结果
go test -bench=. -cpu=1 goroutine_test.go
goos: linux
goarch: amd64
BenchmarkContestSwitch 10000000 144 ns/op
PASS
ok command-line-arguments 1.597s
高效机制背后的秘密
1、goroutine既不是os线程。也不是应用层线程。
2、golang的M:N调度器。
3、golang的轻:一个新创建的goroutine赋予了几千字节,不运行,go会自动缩减内存。
4、goroutine的fork-join模型。