@@ -353,6 +353,120 @@ func Gosched() {
353353 mcall (gosched_m )
354354}
355355
356+ // Yield cooperatively yields if, and only if, the scheduler is "busy".
357+ //
358+ // This can be called by any work wishing to utilize strictly spare capacity
359+ // while minimizing the degree to which it delays other work from being promptly
360+ // scheduled.
361+ //
362+ // Yield is intended to have very low overhead, particularly in its no-op case
363+ // where there is idle capacity in the scheduler and the caller does not need to
364+ // yield. This should allow it to be called often, such as in the body of tight
365+ // loops, in any tasks wishing to yield promptly to any waiting work.
366+ //
367+ // When there is waiting work, the yielding goroutine may briefly be rescheduled
368+ // after it, or may, in some cases, be parked in a waiting 'yield' state until
369+ // the scheduler next has spare capacity to resume it. Yield does not guarantee
370+ // fairness or starvation-prevention: once a goroutine Yields(), it may remain
371+ // parked until the scheduler next has idle capacity. This means Yield can block
372+ // for unbounded durations in the presence of sustained over-saturation; callers
373+ // are responsible for deciding where to Yield() to avoid priority inversions.
374+ //
375+ // Yield will never park if the calling goroutine is locked to an OS thread.
376+ func Yield () {
377+ // Common/fast case: do nothing if npidle is non-zero meaning there there is
378+ // an idle P so no reason to yield this one. Doing only this check here keeps
379+ // Yield inlineable (~70 of 80 as of writing).
380+ if sched .npidle .Load () == 0 {
381+ maybeYield ()
382+ }
383+ }
384+
385+ // maybeYield is called by Yield if npidle is zero, meaning there are no idle Ps
386+ // and thus there may be work to which the caller should yield. Such work could
387+ // be on this local runq of the caller's P, on the global runq, in the runq of
388+ // some other P, or even in the form of ready conns waiting to be noticed by a
389+ // netpoll which would then ready runnable goroutines.
390+ //
391+ // Keeping this function extremely cheap is essential: it must be cheap enough
392+ // that callers can call it in very tight loops, as very frequent calls ensure a
393+ // task wishing to yield when work is waiting will do so promptly. Checking the
394+ // runq of every P or calling netpoll are too expensive to do in every call, so
395+ // given intent is to bound how long work may wait, such checks only need to be
396+ // performed after some amount of time has elapsed (e.g. 0.25ms). To minimize
397+ // overhead when called at a higher frequency, this elapsed time is checked with
398+ // an exponential backoff.
399+ //
400+ // runqs are checked directly with non-atomic reads rather than runqempty: being
401+ // cheap is our top priority and a microsecond of staleness is fine as long as
402+ // the check does not get optimized out of a calling loop body (hence noinline).
403+ //
404+ //go:noinline
405+ func maybeYield () {
406+ gp := getg ()
407+
408+ // Don't park while locked to an OS thread.
409+ if gp .lockedm != 0 {
410+ return
411+ }
412+
413+ if p := gp .m .p .ptr (); p .runqhead != p .runqtail || p .runnext != 0 {
414+ if gp .yieldchecks == 1 {
415+ yieldPark ()
416+ return
417+ }
418+ // To avoid thrashing between yields, set yieldchecks to 1: if we yield
419+ // right back and see this sentinel we'll park instead to break the cycle.
420+ gp .yieldchecks = 1
421+ goyield ()
422+ return
423+ }
424+
425+ // If the global runq is non-empty, park in the global yieldq right away.
426+ if ! sched .runq .empty () {
427+ yieldPark ()
428+ return
429+ }
430+
431+ const yieldCountBits , yieldCountMask = 11 , (1 << 11 ) - 1
432+ const yieldEpochShift = 18 - yieldCountBits
433+ gp .yieldchecks ++
434+ if count := gp .yieldchecks & yieldCountMask ; (count & (count + 1 )) == 0 {
435+ prev := gp .yieldchecks &^ yieldCountMask
436+ now := uint32 (nanotime ()>> yieldEpochShift ) &^ yieldCountMask
437+ if now != prev || count == yieldCountMask {
438+ gp .yieldchecks = now
439+
440+ for i := range allp {
441+ // We don't need the extra accuracy (and cost) of runqempty here either.
442+ if allp [i ].runqhead != allp [i ].runqtail || allp [i ].runnext != 0 {
443+ yieldPark ()
444+ return
445+ }
446+ }
447+
448+ if netpollinited () && netpollAnyWaiters () && sched .lastpoll .Load () != 0 {
449+ var found bool
450+ systemstack (func () {
451+ if list , delta := netpoll (0 ); ! list .empty () {
452+ injectglist (& list )
453+ netpollAdjustWaiters (delta )
454+ found = true
455+ }
456+ })
457+ if found {
458+ goyield ()
459+ }
460+ }
461+ }
462+ }
463+ }
464+
465+ func yieldPark () {
466+ checkTimeouts ()
467+ gopark (yield_put , nil , waitReasonYield , traceBlockPreempted , 1 )
468+ }
469+
356470// goschedguarded yields the processor like gosched, but also checks
357471// for forbidden states and opts out of the yield in those cases.
358472//
@@ -3445,6 +3559,23 @@ top:
34453559 }
34463560 }
34473561
3562+ // Nothing runnable, so check for yielded goroutines parked in yieldq.
3563+ if ! sched .yieldq .empty () {
3564+ lock (& sched .lock )
3565+ bg := sched .yieldq .pop ()
3566+ unlock (& sched .lock )
3567+ if bg != nil {
3568+ trace := traceAcquire ()
3569+ casgstatus (bg , _Gwaiting , _Grunnable )
3570+ if trace .ok () {
3571+ // Match other ready paths for trace visibility.
3572+ trace .GoUnpark (bg , 0 )
3573+ traceRelease (trace )
3574+ }
3575+ return bg , false , false
3576+ }
3577+ }
3578+
34483579 // We have nothing to do.
34493580 //
34503581 // If we're in the GC mark phase, can safely scan and blacken objects,
@@ -3509,6 +3640,10 @@ top:
35093640 unlock (& sched .lock )
35103641 return gp , false , false
35113642 }
3643+ if ! sched .yieldq .empty () {
3644+ unlock (& sched .lock )
3645+ goto top
3646+ }
35123647 if ! mp .spinning && sched .needspinning .Load () == 1 {
35133648 // See "Delicate dance" comment below.
35143649 mp .becomeSpinning ()
@@ -7111,6 +7246,19 @@ func (q *gQueue) popList() gList {
71117246 return stack
71127247}
71137248
7249+ // yield_put is the gopark unlock function for Yield. It enqueues the goroutine
7250+ // onto the global yield queue. Returning true keeps the G parked until another
7251+ // part of the scheduler makes it runnable again. The G remains in _Gwaiting
7252+ // after this returns.
7253+ //
7254+ //go:nosplit
7255+ func yield_put (gp * g , _ unsafe.Pointer ) bool {
7256+ lock (& sched .lock )
7257+ sched .yieldq .pushBack (gp )
7258+ unlock (& sched .lock )
7259+ return true
7260+ }
7261+
71147262// A gList is a list of Gs linked through g.schedlink. A G can only be
71157263// on one gQueue or gList at a time.
71167264type gList struct {
0 commit comments