1
- use std:: cell:: UnsafeCell ;
1
+ use std:: cell:: Cell ;
2
2
use std:: iter;
3
3
use std:: thread;
4
4
use std:: time:: Duration ;
5
5
6
6
use crossbeam_deque:: { Injector , Stealer , Worker } ;
7
7
use once_cell:: sync:: Lazy ;
8
+ use once_cell:: unsync:: OnceCell ;
8
9
9
10
use crate :: task:: executor:: Sleepers ;
10
11
use crate :: task:: Runnable ;
@@ -32,9 +33,18 @@ static POOL: Lazy<Pool> = Lazy::new(|| {
32
33
let worker = Worker :: new_fifo ( ) ;
33
34
stealers. push ( worker. stealer ( ) ) ;
34
35
36
+ let proc = Processor {
37
+ worker,
38
+ slot : Cell :: new ( None ) ,
39
+ slot_runs : Cell :: new ( 0 ) ,
40
+ } ;
41
+
35
42
thread:: Builder :: new ( )
36
43
. name ( "async-std/executor" . to_string ( ) )
37
- . spawn ( || abort_on_panic ( || main_loop ( worker) ) )
44
+ . spawn ( || {
45
+ let _ = PROCESSOR . with ( |p| p. set ( proc) ) ;
46
+ abort_on_panic ( || main_loop ( ) ) ;
47
+ } )
38
48
. expect ( "cannot start a thread driving tasks" ) ;
39
49
}
40
50
@@ -45,59 +55,75 @@ static POOL: Lazy<Pool> = Lazy::new(|| {
45
55
}
46
56
} ) ;
47
57
58
+ /// The state of a worker thread.
59
+ struct Processor {
60
+ /// The local task queue.
61
+ worker : Worker < Runnable > ,
62
+
63
+ /// Contains the next task to run as an optimization that skips queues.
64
+ slot : Cell < Option < Runnable > > ,
65
+
66
+ /// How many times in a row tasks have been taked from the slot rather than the queue.
67
+ slot_runs : Cell < u32 > ,
68
+ }
69
+
48
70
thread_local ! {
49
- /// Local task queue associated with the current worker thread.
50
- static QUEUE : UnsafeCell < Option < Worker < Runnable >>> = UnsafeCell :: new( None ) ;
71
+ /// Worker thread state .
72
+ static PROCESSOR : OnceCell < Processor > = OnceCell :: new( ) ;
51
73
}
52
74
53
75
/// Schedules a new runnable task for execution.
54
76
pub ( crate ) fn schedule ( task : Runnable ) {
55
- QUEUE . with ( |queue| {
56
- let local = unsafe { ( * queue. get ( ) ) . as_ref ( ) } ;
57
-
58
- // If the current thread is a worker thread, push the task into its local task queue.
59
- // Otherwise, push it into the global task queue.
60
- match local {
61
- None => POOL . injector . push ( task) ,
62
- Some ( q) => q. push ( task) ,
77
+ PROCESSOR . with ( |proc| {
78
+ // If the current thread is a worker thread, store it into its task slot or push it into
79
+ // its local task queue. Otherwise, push it into the global task queue.
80
+ match proc. get ( ) {
81
+ Some ( proc) => {
82
+ // Replace the task in the slot.
83
+ if let Some ( task) = proc. slot . replace ( Some ( task) ) {
84
+ // If the slot already contained a task, push it into the local task queue.
85
+ proc. worker . push ( task) ;
86
+ POOL . sleepers . notify_one ( ) ;
87
+ }
88
+ }
89
+ None => {
90
+ POOL . injector . push ( task) ;
91
+ POOL . sleepers . notify_one ( ) ;
92
+ }
63
93
}
64
- } ) ;
65
-
66
- // Notify a sleeping worker that new work just came in.
67
- POOL . sleepers . notify_one ( ) ;
94
+ } )
68
95
}
69
96
70
97
/// Main loop running a worker thread.
71
- fn main_loop ( local : Worker < Runnable > ) {
72
- // Initialize the local task queue.
73
- QUEUE . with ( |queue| unsafe { * queue. get ( ) = Some ( local) } ) ;
98
+ fn main_loop ( ) {
99
+ /// Number of yields when no runnable task is found.
100
+ const YIELDS : u32 = 3 ;
101
+ /// Number of short sleeps when no runnable task in found.
102
+ const SLEEPS : u32 = 1 ;
74
103
75
104
// The number of times the thread didn't find work in a row.
76
- let mut step = 0 ;
105
+ let mut fails = 0 ;
77
106
78
107
loop {
79
108
// Try to find a runnable task.
80
109
match find_runnable ( ) {
81
110
Some ( task) => {
82
- // Found. Now run the task.
111
+ fails = 0 ;
112
+
113
+ // Run the found task.
83
114
task. run ( ) ;
84
- step = 0 ;
85
115
}
86
116
None => {
117
+ fails += 1 ;
118
+
87
119
// Yield the current thread or put it to sleep.
88
- match step {
89
- 0 ..=2 => {
90
- thread:: yield_now ( ) ;
91
- step += 1 ;
92
- }
93
- 3 => {
94
- thread:: sleep ( Duration :: from_micros ( 10 ) ) ;
95
- step += 1 ;
96
- }
97
- _ => {
98
- POOL . sleepers . wait ( ) ;
99
- step = 0 ;
100
- }
120
+ if fails <= YIELDS {
121
+ thread:: yield_now ( ) ;
122
+ } else if fails <= YIELDS + SLEEPS {
123
+ thread:: sleep ( Duration :: from_micros ( 10 ) ) ;
124
+ } else {
125
+ POOL . sleepers . wait ( ) ;
126
+ fails = 0 ;
101
127
}
102
128
}
103
129
}
@@ -106,29 +132,42 @@ fn main_loop(local: Worker<Runnable>) {
106
132
107
133
/// Find the next runnable task.
108
134
fn find_runnable ( ) -> Option < Runnable > {
109
- let pool = & * POOL ;
110
-
111
- QUEUE . with ( |queue| {
112
- let local = unsafe { ( * queue. get ( ) ) . as_ref ( ) . unwrap ( ) } ;
135
+ /// Maximum number of times the slot can be used in a row.
136
+ const SLOT_LIMIT : u32 = 16 ;
137
+
138
+ PROCESSOR . with ( |proc| {
139
+ let proc = proc. get ( ) . unwrap ( ) ;
140
+
141
+ // Try taking a task from the slot.
142
+ let runs = proc. slot_runs . get ( ) ;
143
+ if runs < SLOT_LIMIT {
144
+ if let Some ( task) = proc. slot . take ( ) {
145
+ proc. slot_runs . set ( runs + 1 ) ;
146
+ return Some ( task) ;
147
+ }
148
+ }
149
+ proc. slot_runs . set ( 0 ) ;
113
150
114
151
// Pop a task from the local queue, if not empty.
115
- local . pop ( ) . or_else ( || {
152
+ proc . worker . pop ( ) . or_else ( || {
116
153
// Otherwise, we need to look for a task elsewhere.
117
154
iter:: repeat_with ( || {
118
155
// Try stealing a batch of tasks from the global queue.
119
- pool . injector
120
- . steal_batch_and_pop ( & local )
156
+ POOL . injector
157
+ . steal_batch_and_pop ( & proc . worker )
121
158
// Or try stealing a batch of tasks from one of the other threads.
122
159
. or_else ( || {
123
160
// First, pick a random starting point in the list of local queues.
124
- let len = pool . stealers . len ( ) ;
161
+ let len = POOL . stealers . len ( ) ;
125
162
let start = random ( len as u32 ) as usize ;
126
163
127
164
// Try stealing a batch of tasks from each local queue starting from the
128
165
// chosen point.
129
- let ( l, r) = pool. stealers . split_at ( start) ;
130
- let rotated = r. iter ( ) . chain ( l. iter ( ) ) ;
131
- rotated. map ( |s| s. steal_batch_and_pop ( & local) ) . collect ( )
166
+ let ( l, r) = POOL . stealers . split_at ( start) ;
167
+ let stealers = r. iter ( ) . chain ( l. iter ( ) ) ;
168
+ stealers
169
+ . map ( |s| s. steal_batch_and_pop ( & proc. worker ) )
170
+ . collect ( )
132
171
} )
133
172
} )
134
173
// Loop while no task was stolen and any steal operation needs to be retried.
0 commit comments