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)


posted on 2022-05-08 22:34  运维开发玄德公  阅读(11)  评论(0编辑  收藏  举报  来源

导航