In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly explains "when Go will preempt P", interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "when Go will seize P"!
Development History of Scheduler
In the Go language, Goroutine was not designed to be preemptive in the early days, and scheduling switching was triggered only by read-write, active surrender, lock and other operations in the early Goroutine.
There is a serious problem with this, that is, when the garbage collector STW, if a Goroutine has been blocking the call, the garbage collector will wait for him all the time.
In this case, preemptive scheduling is needed to solve the problem. If a Goroutine is running for too long, it needs to be preempted to solve it.
This Go language began to implement a preemptive scheduler from Go1.2 and has been continuously improved to this day:
Go0.x: a single-threaded scheduler.
Go1.0: a scheduler based on multithreading.
Go1.1: a scheduler that is stolen based on tasks.
Go1.2-Go1.13: a collaboration-based preemptive scheduler.
Go1.14: signal-based preemptive scheduler.
The new proposal of the scheduler: non-uniform memory access scheduling (Non-uniform memory access,NUMA), but it has not been put on the agenda because the implementation is too complex and the priority is not high enough.
For those who are interested, please see the NUMA-aware scheduler for Go proposed by Dmitry Vyukov and dvyukov.
Why preempt P
Why do you want to seize P? to put it bluntly, if you don't grab it, you won't have a chance to run it and hang will die. Or the resources are unevenly distributed.
This is obviously unreasonable in the design of the scheduler.
Just like this example:
/ / Main Goroutine func main () {/ / simulated mononuclear CPU runtime.GOMAXPROCS (1) / / simulated Goroutine endless cycle go func () {for {}} () time.Sleep (time.Millisecond) fmt.Println ("brain fried fish")}
This example was blocked all the time in the old version of the Go language, unable to see the light of day, and was a scenario that needed to be preempted.
However, some friends may ask whether there will be any new problems if they seize it. Because M, which is already using P, is cool (M binds to P), it can't continue without P.
In fact, this is no problem, because the Goroutine has been blocked on the system call, there will be no subsequent implementation of the new request.
But what if the code can run again after running for a long time (and the business allows a long wait), that is, it's time for the Goroutine to recover from the blocking state and expect to continue to run, without P?
At this time, the Goroutine can, like other Goroutine, first check whether the M it belongs to is still bound to P:
Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community
If there is a P, you can adjust the state and continue to run.
If there is no P, you can grab P again, and then occupy and bind P for your own use.
That is, seizing P, itself is a two-way behavior, you robbed my P, I can also grab other people's P to continue to run.
How to preempt P
After explaining why we want to seize P, we dig deeper, how did "he" seize the specific P?
This involves the runtime.retake method mentioned earlier, which handles the following two scenarios:
Preempt the P blocking on the system call.
Preempt G that has been running for too long.
For the scenario of preemption P, the analysis is as follows:
Func retake (now int64) uint32 {n: = 0 / / to prevent changes, lock lock (& allpLock) / / for all P into the main logic, and start processing for I: = 0; I for all P
< len(allp); i++ { _p_ := allp[i] pd := &_p_.sysmontick s := _p_.status sysretake := false ... if s == _Psyscall { // 判断是否超过 1 个 sysmon tick 周期 t := int64(_p_.syscalltick) if !sysretake && int64(pd.syscalltick) != t { pd.syscalltick = uint32(t) pd.syscallwhen = now continue } ... } } unlock(&allpLock) return uint32(n) } 该方法会先对 allpLock 上锁,这个变量含义如其名,allpLock 可以防止该数组发生变化。 其会保护 allp、idlepMask 和 timerpMask 属性的无 P 读取和大小变化,以及对 allp 的所有写入操作,可以避免影响后续的操作。 场景一 前置处理完毕后,进入主逻辑,会使用万能的 for 循环对所有的 P(allp)进行一个个处理。 t := int64(_p_.syscalltick) if !sysretake && int64(pd.syscalltick) != t { pd.syscalltick = uint32(t) pd.syscallwhen = now continue } 第一个场景是:会对 syscalltick 进行判定,如果在系统调用(syscall)中存在超过 1 个 sysmon tick 周期(至少 20us)的任务,则会从系统调用中抢占 P,否则跳过。 场景二 如果未满足会继续往下,走到如下逻辑: func retake(now int64) uint32 { for i := 0; i < len(allp); i++ { ... if s == _Psyscall { // 从此处开始分析 if runqempty(_p_) && atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) >0 & & pd.syscallwhen+10*1000*1000 > now {continue}...} unlock (& allpLock) return uint32 (n)}
The second scene focuses on this long list of judgments:
The runqempty (_ p _) = = true method determines whether the task queue P is empty to detect whether there are any other tasks that need to be performed.
Atomic.Load (& sched.nmspinning) + atomic.Load (& sched.npidle) > 0 determines whether there is a free P and a P that is scheduling to steal G.
Pd.syscallwhen+10*1000*1000 > now determines whether the system call time exceeds 10ms.
The strange thing here is that the runqempty method has clearly determined that there are no other tasks, which means that there are no tasks to perform and there is no need to snatch P.
But the reality is that you want to continue to occupy P in the end because it may prevent the sysmon thread from sleeping deeply.
After completing the above judgment, we enter the stage of snatching P:
Func retake (now int64) uint32 {for I: = 0; I < len (allp); iTunes + {... If s = = _ Psyscall {/ / undertake the first half of unlock (& allpLock) incidlelocked (- 1) if atomic.Cas (& _ p_.status, s _ Pidle) {if trace.enabled {traceGoSysBlock (_ p _) traceProcStop (_ p _)} handoffp (_ p _)} incidlelocked (1) lock (& allpLock)}} unlock (& allpLock) return uint32 (n)}
Unlock related properties: you need to call the unlock method to unlock the allpLock to get the sched.lock in order to proceed to the next step.
Reduce idle M: you need to reduce the number of idle M before the atomic operation (CAS) (assuming one is running). Otherwise, when a snatch M occurs, you may exit the system call, increment the nmidle, and report a deadlock event.
Modify the P state: call the atomic.Cas method to set the grabbed P state to idle so that it can be used by other M.
Snatch P and Regulatory M: call the handoffp method to snatch P from the system call or locked M, and the new M takes over the P.
At this point, I believe you have a deeper understanding of "when Go will preempt P". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.