拾遗笔记

goroutine调度 拾遗

拾遗

go的调度本质上是一个协作式调度器,后来加入了些抢占机制,但并不能
保障一定能抢占的到sysmon 线程会定期扫goroutine ,如果running>10ms则将其
标记为可被抢占注意只是标记为,需要在这个goroutine调用非内联函数时才能
真正的触发把执行权交出去,即如果此goroutine没有调用任何函数,纯粹是
cpu密集型计算,则并不会把执行权让出去。

系统调用阻塞时(同步系统调用)

当G被阻塞在某个系统调用上时(并不是所有的系统调用都是阻塞的),此时G
会阻塞在_Gsyscall状态,M也处于 block on syscall 状态,此时的M可被操作
系统抢占调度(即让出cpu),执行该G的M会与P解绑(注意此时G仍然与M绑在一起,
等着系统调用的返回),而P则尝试与其它idle的M绑定,继续执行其它G。如果没
有其它idle的M,但P的Local队列中仍然有G需要执行,则创建一个新的M(即P总
会给它分配一个M,不会让它闲着);

当系统调用完成后,G会重新尝试获取一个idle的P加入它的Local队列,恢复
执行,如果没有idle的P,G会被标记为runnable加入到Global队列

net poller

除了G P M 角色golang 专门有个net poller 用来处理网络IO,即如果某
G1想要进行网络系统调用,则它会从P的队列中移除,并被移动到网络轮询器的队列中
处理异步网络系统调用。然后,M可以从其关联P的 local 队列中选择另一个G执行执行其中的代码。

异步网络系统调用由网络轮询器完成后,G1被移回到P的 local 队列 中

用户态阻塞/唤醒

channel操作

network I/O

golang已经用netpoller实现了goroutine网络I/O阻塞不会导致M被阻塞,仅阻塞G,
对应的G会被放置到某个wait队列(如channel的waitq)

调度器跟踪调试

G ODEBUG=scheddetail=1,schedtrace=1000 ./program

SCHED 0ms: gomaxprocs=8 idleprocs=7 threads=2 spinningthreads=0 idlethreads=0 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=1 schedtick=0 syscalltick=0 m=0 runqsize=0 gfreecnt=0
P1: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P2: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P3: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P4: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P5: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P6: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P7: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
M1: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
M0: p=0 curg=1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 helpgc=0 spinning=false blocked=false lockedg=1
G1: status=8() m=0 lockedm=0

goroutine的创建

proc.go newproc

  func newproc(siz int32, fn *funcval) {

  func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) {
	runqput(_p_, newg, true)

    // runqput tries to put g on the local runnable queue.
    // If next is false, runqput adds g to the tail of the runnable queue.
    // If next is true, runqput puts g in the _p_.runnext slot.
    // If the run queue is full, runnext puts g on the global queue.
    // Executed only by the owner P.
func runqput(_p_ *p, gp *g, next bool) {
	if randomizeScheduler && next && fastrand()%2 == 0 {
		next = false
	}

	if next {
	retryNext:
		oldnext := _p_.runnext
	//这里会把新创建的g放到_p_.runnext上,以更高优先级被执行(高于_p_.runq队列里的)
		if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
			goto retryNext
		}
		if oldnext == 0 {
			return
		}
		// Kick the old runnext out to the regular run queue.
		gp = oldnext.ptr()
	}


这就能解释下面这段程序的打印顺序问题了

package main

import (
	"fmt"
	"runtime"
)

func main() {
	runtime.GOMAXPROCS(1)
	var c chan int = make(chan int, 100)

    g
o func() { c <- 1 }()
	go func() { c <- 2 }()
	go func() { c <- 3 }()
	go func() { c <- 4 }()
	go func() { c <- 5 }()
	go func() { c <- 6 }()
	go func() {
		for {
		}
	}()
	go func() { c <- 8 }()
	go func() { c <- 9 }()
	go func() { c <- 10 }()

	for {
		select {
		case v := <-c:
			fmt.Println("sssssssssssssssssssss", v)

		}

	}

}

go run a.go
sssssssssssssssssssss 10 p.runnext先被执行
sssssssssssssssssssss 1
sssssssssssssssssssss 2
sssssssssssssssssssss 3
sssssssssssssssssssss 4
sssssssssssssssssssss 5
sssssssssssssssssssss 6

Comments

comments powered by Disqus