前言:
golang的runtime提供了一个LockOSThread的函数,该方法的作用是可以让当前协程绑定并独立一个线程 M。 那么绑定线程的那个协程new出来的子协程在哪里跑? 先说答案,子协程不会继承lockOSThread特性。 是否是这样,我们可以试一下 …
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# xiaorui.cc package main import ( "fmt" "net/http" "runtime" "syscall" "time" "github.com/gin-gonic/gin" ) func main() { runtime.LockOSThread() router := gin.Default() router.GET("/ping", func(c *gin.Context) { fmt.Println("worker:", syscall.Gettid()) c.JSON(200, gin.H{ "message": "pong", }) }) s := &http.Server{ Addr: ":18282", Handler: router, ReadTimeout: 600 * time.Second, WriteTimeout: 600 * time.Second, MaxHeaderBytes: 1 << 20, } fmt.Println("listen:", syscall.Gettid()) s.ListenAndServe() } |
注释, 通过 syscall.Gettid()是可以获取当前G绑定的线程id (lwp).
测试代码的日志结果很明显,虽然我们在main主协程里配了runtime.LockOSThread()线程绑定,但因为net/http底层会针对每个accept后的连接new一个子协程,这些子协程不会继承父协程的LockOSThread线程绑定。
除了syscall方法外,我们还可以 include <pthread.h> 方法来打印线程标识,C.phread_self() 来输出线程标识。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main // #include <pthread.h> import "C" import ( "fmt" "runtime" ) // xiaorui.cc func main() { runtime.GOMAXPROCS(runtime.NumCPU()) ch1 := make(chan bool) ch2 := make(chan bool) fmt.Println("main", C.pthread_self()) go func() { runtime.LockOSThread() fmt.Println("locked", C.pthread_self()) go func() { fmt.Println("locked child", C.pthread_self()) ch1 <- true }() ch2 <- true }() <-ch1 <-ch2 } |
为什么golang的子协程不能继承main goroutine的线程绑定?
因为官方doc有runtime.LockOSThread线程方面的描述,包括子协程. https://tip.golang.org/doc/go1.10#runtime
1 2 3 4 5 6 7 8 9 10 11 |
# xiaorui.cc The behavior of nested calls to LockOSThread and UnlockOSThread has changed. These functions control whether a goroutine is locked to a specific operating system thread, so that the goroutine only runs on that thread, and the thread only runs that goroutine. Previously, calling LockOSThread more than once in a row was equivalent to calling it once, and a single UnlockOSThread always unlocked the thread. Now, the calls nest: if LockOSThread is called multiple times, UnlockOSThread must be called the same number of times in order to unlock the thread. Existing code that was careful not to nest these calls will remain correct. Existing code that incorrectly assumed the calls nested will become correct. Most uses of these functions in public Go source code falls into the second category. |
golang nuts和issuse里有不少帖子是关于子协程线程绑定的,看样子作者是没有兴趣做这方面的扩展,主要会影响当前的go的调度策略。 有兴趣的朋友可以追看下issuse页 https://github.com/golang/go/issues/23758
什么场景下用到runtime.LockOSThread ?
我们知道golang的scheduler可以理解为公平协作调度和抢占的综合体,他不支持优先级调度。当你开了几十万个goroutine,并且大多数协程已经在runq等待调度了, 那么如果你有一个重要的周期性的协程需要优先执行该怎么办?
可以借助runtime.LockOSThread()方法来绑定线程,绑定线程M后的好处在于,他可以由system kernel内核来调度,因为他本质是线程了。
先前我们有在定时器场景中使用runtime.LockOSThread,达到少许的优先级效果。效果不明显的原因是,我们自定义的定时器需要time.sleep来解决cpu忙讯轮,但time.sleep又依赖于go自身的heap定时器…. 解决方法是,独立一个M线程后,使用syscall来实现时间等待.
总结:
runtime.LockOSThread会锁定当前协程只跑在一个系统线程上,这个线程里也只跑该协程。他们是相互牵制的 !!!
有疑问加站长微信联系(非本文作者)