golang并发编程-03-协程(Goroutine)概述
1. 协程(Goroutine)
Goroutine——协程,有的人跟我说不是,我不想反驳,也不打算改。
1.1 一些相关概念
下边这些概念之前提到过,这里需要用到,所以再诠释一遍。
1)线程的三个核心元素:
- M:Machine的缩写。一个M代表了一个内核线程。
- P:Processor的缩写。一个P代表了M所需的上下文环境。
- G:Goroutine的缩写。一个G代表了对一段需要被并发执行的Go语言代码的封装。
2)KSE
- 内核调度实体
- 在一个M的生命周期内,它会且仅会与一个KSE产生关联。
1.2 协程的使用
之前在 《go语言基础-07-协程-01-并发》里边讲过协程的基本使用。可以回翻。
1.3 一个示例
1)用协程打印结果
func main() {
names := []string{"关羽", "张飞", "赵云", "马超","黄忠"}
for _,name := range names {
go fmt.Printf("Hello %s.\n", name)
time.Sleep(5 * time.Second)
runtime.Gosched()
}
说明:
如果不加sleep,协程们还没有打印完,主线程就结束了,结果打出几条得看命。
当然也可以用 waitgroup 我们后边会讲到
2)换成协程调用函数
func main() {
names := []string{"关羽", "张飞", "赵云", "马超","黄忠"}
for _,name := range names {
go func() {
fmt.Printf("Hello %s.\n", name)
}()
}
time.Sleep(5 * time.Second)
runtime.Gosched()
}
和上边的区别是协程调用了一个函数,你猜打印结果是什么?
答案是:没准!
解释:协程调用的函数启动时,主线程的for循环跑到哪儿就接收哪个变量。
Hello 赵云.
Hello 黄忠.
Hello 黄忠.
Hello 黄忠.
Hello 赵云.
3)用sleep拉开时间
这种方法是我瞎说的,虽然结果对,但是没这么干的,还不如不用协程。
func main() {
names := []string{"关羽", "张飞", "赵云", "马超","黄忠"}
for _,name := range names {
go func() {
fmt.Printf("Hello %s.\n", name)
}()
time.Sleep(1 * time.Second)
}
runtime.Gosched()
}
4)正确写法
for循环里设置一个变量tmp接收name值,变量值不会传到本次循环外,因此保证了协程的正确打印。
func main() {
names := []string{"关羽", "张飞", "赵云", "马超","黄忠"}
for _,name := range names {
tmp := name
go func(tmp string) {
fmt.Printf("Hello %s.\n", tmp)
}(tmp)
}
time.Sleep(3*time.Second)
runtime.Gosched()
}
输出
Hello 关羽.
Hello 马超.
Hello 赵云.
Hello 张飞.
Hello 黄忠.
但是多打印几次,你会发现顺序是乱的。
按顺序打印我们会在channel 一章里边说。
2. runtime包
2.1 GOMAXPROCS函数
- 作用:应用程序可以在运行期间设置运行时系统中的P的最大数量。
- 范围:1~256的范围内
- 应用:
建议尽早调用(main函数开始时或init函数中)
程序可以在运行期间设置运行时系统中的P的最大数量,但由于这样做会引起“Stop the world”。
2.2 Goexit函数
- 作用:
该函数被调用,会立即使调用它的Goroutine的运行终止。
但该Goroutine中未执行的defer语句会被执行。
该函数会把被终止运行的Goroutine置于Gdead状态,并将其放入调度器的自由G列表。这样,调度器可以在有需要时重新启用此Goroutine。最后,应用程序对runtime.Goexit函数的调用还会触发调度器的一轮调度流程。
2.3 Gosched函数
- 作用:暂停调用它的Goroutine的运行。
调用它的Goroutine会被重新置于Grunnable状态,并被放入到调度器的可运行G队列中。这也是使用“暂停”这个词的原因。因为经过调度器的调度,该Goroutine不久就会再次被运行。这样做完全是为了让其他Goroutine立即有被运行的机会。
2.4 NumGoroutine函数
- 作用:返回运行时系统中的处于特定状态的Goroutine的数量
- 特定状态:
Grunnable、Grunning、Gsyscall、Gwaiting
2.5 LockOSThread / UnlockOSThread函数
- LockOSThread:将Goroutine与当前运行它的M锁定在一起
- UnlockOSThread:解锁Goroutine与当前运行它的M
2.6 runtime/debug.SetMaxStack函数
- 作用:约束单个Goroutine所能申请的栈空间的最大尺寸
- 使用:在main函数以及main包的init函数真正被执行之前,主Goroutine会对此进行默认的设置。
2.7 runtime/debug.SetMaxThreads函数
- 作用:设置运行时系统所使用的内核线程的数量
2.8 与垃圾回收相关函数
- runtime.GC
运行时系统进行一次强制性的垃圾收集。 - runtime/debug.SetGCPercent
设置垃圾收集比率以进行垃圾收集。
具体计算公式如下:
<触发垃圾收集的堆内存单元增量> = <上一次垃圾收集完成后的堆内存单元数量> * (<垃圾收集比率> / 100)