重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
通过runtime.GOMAXPROCS函数,应用程序何以在运行期间设置运行时系统中得P最大数量。但这会引起“Stop the Word”。所以,应在应用程序最早的调用。并且最好的设置P最大值的方法是在运行Go程序之前设置好操作程序的环境变量GOMAXPROCS,而不是在程序中调用runtime.GOMAXPROCS函数。
创新互联-专业网站定制、快速模板网站建设、高性价比荣成网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式荣成网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖荣成地区。费用合理售后完善,十余年实体公司更值得信赖。
最后记住,无论我们传递给函数的整数值是什么值,运行时系统的P最大值总会在1~256之间。
runtime.Goexit函数被调用后,会立即使调用他的Groution的运行被终止,但其他Goroutine并不会受到影响。runtime.Goexit函数在终止调用它的Goroutine的运行之前会先执行该Groution中还没有执行的defer语句。
runtime.Gosched函数的作用是暂停调用他的Goroutine的运行,调用他的Goroutine会被重新置于Gorunnable状态,并被放入调度器可运行G队列中。
runtime.NumGoroutine函数在被调用后,会返回系统中的处于特定状态的Goroutine的数量。这里的特指是指Grunnable\Gruning\Gsyscall\Gwaition。处于这些状态的Groutine即被看做是活跃的或者说正在被调度。
注意:垃圾回收所在Groutine的状态也处于这个范围内的话,也会被纳入该计数器。
前者调用会使调用他的Goroutine与当前运行它的M锁定到一起,后者调用会解除这样的锁定。
注意:
debug.SetMaxStack函数的功能是约束单个Groutine所能申请的栈空间的最大尺寸。
debug.SetMaxThreads函数的功能是对go语言运行时系统所使用的内核线程的数量(确切的说是M的数量)进行设置
会让运行时系统进行一次强制性的垃圾收集,
用于设置一个比率(垃圾收集比率),前面所说的单元增量与前一次垃圾收集时的岁内存的单元数量和此垃圾手机比率有关。
触发垃圾收集的堆内存单元增量=上一次垃圾收集完的堆内存单元数量*(垃圾收集比率/100)
本文是对 Gopher 2017 中一个非常好的 Talk�: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的学习笔记,希望能够通过对 channel 的关键特性的理解,进一步掌握其用法细节以及 Golang 语言设计哲学的管窥蠡测。
channel 是可以让一个 goroutine 发送特定值到另一个 gouroutine 的通信机制。
原生的 channel 是没有缓存的(unbuffered channel),可以用于 goroutine 之间实现同步。
关闭后不能再写入,可以读取直到 channel 中再没有数据,并返回元素类型的零值。
gopl/ch3/netcat3
首先从 channel 是怎么被创建的开始:
在 heap 上分配一个 hchan 类型的对象,并将其初始化,然后返回一个指向这个 hchan 对象的指针。
理解了 channel 的数据结构实现,现在转到 channel 的两个最基本方法: sends 和 receivces ,看一下以上的特性是如何体现在 sends 和 receives 中的:
假设发送方先启动,执行 ch - task0 :
如此为 channel 带来了 goroutine-safe 的特性。
在这样的模型里, sender goroutine - channel - receiver goroutine 之间, hchan 是唯一的共享内存,而这个唯一的共享内存又通过 mutex 来确保 goroutine-safe ,所有在队列中的内容都只是副本。
这便是著名的 golang 并发原则的体现:
发送方 goroutine 会阻塞,暂停,并在收到 receive 后才恢复。
goroutine 是一种 用户态线程 , 由 Go runtime 创建并管理,而不是操作系统,比起操作系统线程来说,goroutine更加轻量。
Go runtime scheduler 负责将 goroutine 调度到操作系统线程上。
runtime scheduler 怎么将 goroutine 调度到操作系统线程上?
当阻塞发生时,一次 goroutine 上下文切换的全过程:
然而,被阻塞的 goroutine 怎么恢复过来?
阻塞发生时,调用 runtime sheduler 执行 gopark 之前,G1 会创建一个 sudog ,并将它存放在 hchan 的 sendq 中。 sudog 中便记录了即将被阻塞的 goroutine G1 ,以及它要发送的数据元素 task4 等等。
接收方 将通过这个 sudog 来恢复 G1
接收方 G2 接收数据, 并发出一个 receivce ,将 G1 置为 runnable :
同样的, 接收方 G2 会被阻塞,G2 会创建 sudoq ,存放在 recvq ,基本过程和发送方阻塞一样。
不同的是,发送方 G1如何恢复接收方 G2,这是一个非常神奇的实现。
理论上可以将 task 入队,然后恢复 G2, 但恢复 G2后,G2会做什么呢?
G2会将队列中的 task 复制出来,放到自己的 memory 中,基于这个思路,G1在这个时候,直接将 task 写到 G2的 stack memory 中!
这是违反常规的操作,理论上 goroutine 之间的 stack 是相互独立的,只有在运行时可以执行这样的操作。
这么做纯粹是出于性能优化的考虑,原来的步骤是:
优化后,相当于减少了 G2 获取锁并且执行 memcopy 的性能消耗。
channel 设计背后的思想可以理解为 simplicity 和 performance 之间权衡抉择,具体如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的实现,使用锁的队列实现更简单,容易实现
可以参考下这个。在stack scan阶段有一小段stw,和mark termination阶段要stw。之前要stw的mark和sweep阶段都是并行的,不需要stw的了。
里面也提到了1.5使用了write barrier的算法会导致吞吐量下降,1.6会根据实际使用情况平衡下延迟和吞吐量。
没有stw也是可以的,但吞吐量会进一步下降,未必是最佳选择。