@@ -50,33 +50,64 @@ var modinfo string
50
50
// any work to do.
51
51
//
52
52
// The current approach:
53
- // We unpark an additional thread when we ready a goroutine if (1) there is an
54
- // idle P and there are no "spinning" worker threads. A worker thread is considered
55
- // spinning if it is out of local work and did not find work in global run queue/
56
- // netpoller; the spinning state is denoted in m.spinning and in sched.nmspinning.
57
- // Threads unparked this way are also considered spinning; we don't do goroutine
58
- // handoff so such threads are out of work initially. Spinning threads do some
59
- // spinning looking for work in per-P run queues before parking. If a spinning
53
+ //
54
+ // This approach applies to three primary sources of potential work: readying a
55
+ // goroutine, new/modified-earlier timers, and idle-priority GC. See below for
56
+ // additional details.
57
+ //
58
+ // We unpark an additional thread when we submit work if (this is wakep()):
59
+ // 1. There is an idle P, and
60
+ // 2. There are no "spinning" worker threads.
61
+ //
62
+ // A worker thread is considered spinning if it is out of local work and did
63
+ // not find work in the global run queue or netpoller; the spinning state is
64
+ // denoted in m.spinning and in sched.nmspinning. Threads unparked this way are
65
+ // also considered spinning; we don't do goroutine handoff so such threads are
66
+ // out of work initially. Spinning threads spin on looking for work in per-P
67
+ // run queues and timer heaps or from the GC before parking. If a spinning
60
68
// thread finds work it takes itself out of the spinning state and proceeds to
61
- // execution. If it does not find work it takes itself out of the spinning state
62
- // and then parks.
63
- // If there is at least one spinning thread (sched.nmspinning>1), we don't unpark
64
- // new threads when readying goroutines. To compensate for that, if the last spinning
65
- // thread finds work and stops spinning, it must unpark a new spinning thread.
66
- // This approach smooths out unjustified spikes of thread unparking,
67
- // but at the same time guarantees eventual maximal CPU parallelism utilization.
69
+ // execution. If it does not find work it takes itself out of the spinning
70
+ // state and then parks.
71
+ //
72
+ // If there is at least one spinning thread (sched.nmspinning>1), we don't
73
+ // unpark new threads when submitting work. To compensate for that, if the last
74
+ // spinning thread finds work and stops spinning, it must unpark a new spinning
75
+ // thread. This approach smooths out unjustified spikes of thread unparking,
76
+ // but at the same time guarantees eventual maximal CPU parallelism
77
+ // utilization.
78
+ //
79
+ // The main implementation complication is that we need to be very careful
80
+ // during spinning->non-spinning thread transition. This transition can race
81
+ // with submission of new work, and either one part or another needs to unpark
82
+ // another worker thread. If they both fail to do that, we can end up with
83
+ // semi-persistent CPU underutilization.
84
+ //
85
+ // The general pattern for submission is:
86
+ // 1. Submit work to the local run queue, timer heap, or GC state.
87
+ // 2. #StoreLoad-style memory barrier.
88
+ // 3. Check sched.nmspinning.
68
89
//
69
- // The main implementation complication is that we need to be very careful during
70
- // spinning->non-spinning thread transition. This transition can race with submission
71
- // of a new goroutine, and either one part or another needs to unpark another worker
72
- // thread. If they both fail to do that, we can end up with semi-persistent CPU
73
- // underutilization. The general pattern for goroutine readying is: submit a goroutine
74
- // to local work queue, #StoreLoad-style memory barrier, check sched.nmspinning.
75
- // The general pattern for spinning->non-spinning transition is: decrement nmspinning,
76
- // #StoreLoad-style memory barrier, check all per-P work queues for new work.
77
- // Note that all this complexity does not apply to global run queue as we are not
78
- // sloppy about thread unparking when submitting to global queue. Also see comments
79
- // for nmspinning manipulation.
90
+ // The general pattern for spinning->non-spinning transition is:
91
+ // 1. Decrement nmspinning.
92
+ // 2. #StoreLoad-style memory barrier.
93
+ // 3. Check all per-P work queues and GC for new work.
94
+ //
95
+ // Note that all this complexity does not apply to global run queue as we are
96
+ // not sloppy about thread unparking when submitting to global queue. Also see
97
+ // comments for nmspinning manipulation.
98
+ //
99
+ // How these different sources of work behave varies, though it doesn't affect
100
+ // the synchronization approach:
101
+ // * Ready goroutine: this is an obvious source of work; the goroutine is
102
+ // immediately ready and must run on some thread eventually.
103
+ // * New/modified-earlier timer: The current timer implementation (see time.go)
104
+ // uses netpoll in a thread with no work available to wait for the soonest
105
+ // timer. If there is no thread waiting, we want a new spinning thread to go
106
+ // wait.
107
+ // * Idle-priority GC: The GC wakes a stopped idle thread to contribute to
108
+ // background GC work (note: currently disabled per golang.org/issue/19112).
109
+ // Also see golang.org/issue/44313, as this should be extended to all GC
110
+ // workers.
80
111
81
112
var (
82
113
m0 m
@@ -2785,18 +2816,25 @@ stop:
2785
2816
pidleput (_p_ )
2786
2817
unlock (& sched .lock )
2787
2818
2788
- // Delicate dance: thread transitions from spinning to non-spinning state,
2789
- // potentially concurrently with submission of new goroutines. We must
2790
- // drop nmspinning first and then check all per-P queues again (with
2791
- // #StoreLoad memory barrier in between). If we do it the other way around,
2792
- // another thread can submit a goroutine after we've checked all run queues
2793
- // but before we drop nmspinning; as a result nobody will unpark a thread
2794
- // to run the goroutine.
2819
+ // Delicate dance: thread transitions from spinning to non-spinning
2820
+ // state, potentially concurrently with submission of new work. We must
2821
+ // drop nmspinning first and then check all sources again (with
2822
+ // #StoreLoad memory barrier in between). If we do it the other way
2823
+ // around, another thread can submit work after we've checked all
2824
+ // sources but before we drop nmspinning; as a result nobody will
2825
+ // unpark a thread to run the work.
2826
+ //
2827
+ // This applies to the following sources of work:
2828
+ //
2829
+ // * Goroutines added to a per-P run queue.
2830
+ // * New/modified-earlier timers on a per-P timer heap.
2831
+ // * Idle-priority GC work (barring golang.org/issue/19112).
2832
+ //
2795
2833
// If we discover new work below, we need to restore m.spinning as a signal
2796
2834
// for resetspinning to unpark a new worker thread (because there can be more
2797
2835
// than one starving goroutine). However, if after discovering new work
2798
- // we also observe no idle Ps, it is OK to just park the current thread:
2799
- // the system is fully loaded so no spinning threads are required.
2836
+ // we also observe no idle Ps it is OK to skip unparking a new worker
2837
+ // thread: the system is fully loaded so no spinning threads are required.
2800
2838
// Also see "Worker thread parking/unparking" comment at the top of the file.
2801
2839
wasSpinning := _g_ .m .spinning
2802
2840
if _g_ .m .spinning {
0 commit comments