88
99use crate :: future:: Future ;
1010use crate :: loom:: cell:: UnsafeCell ;
11- use crate :: loom:: sync:: Mutex ;
1211use crate :: runtime:: task:: { JoinHandle , LocalNotified , Notified , Schedule , Task } ;
13- use crate :: util:: linked_list:: { CountedLinkedList , Link , LinkedList } ;
12+ use crate :: util:: linked_list:: { Link , LinkedList } ;
13+ use crate :: util:: sharded_list;
1414
15+ use crate :: loom:: sync:: atomic:: { AtomicBool , Ordering } ;
1516use std:: marker:: PhantomData ;
1617use std:: num:: NonZeroU64 ;
1718
@@ -25,7 +26,7 @@ use std::num::NonZeroU64;
2526// mixed up runtimes happen to have the same id.
2627
2728cfg_has_atomic_u64 ! {
28- use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
29+ use std:: sync:: atomic:: AtomicU64 ;
2930
3031 static NEXT_OWNED_TASKS_ID : AtomicU64 = AtomicU64 :: new( 1 ) ;
3132
@@ -40,7 +41,7 @@ cfg_has_atomic_u64! {
4041}
4142
4243cfg_not_has_atomic_u64 ! {
43- use std:: sync:: atomic:: { AtomicU32 , Ordering } ;
44+ use std:: sync:: atomic:: AtomicU32 ;
4445
4546 static NEXT_OWNED_TASKS_ID : AtomicU32 = AtomicU32 :: new( 1 ) ;
4647
@@ -55,30 +56,30 @@ cfg_not_has_atomic_u64! {
5556}
5657
5758pub ( crate ) struct OwnedTasks < S : ' static > {
58- inner : Mutex < CountedOwnedTasksInner < S > > ,
59+ list : List < S > ,
5960 pub ( crate ) id : NonZeroU64 ,
61+ closed : AtomicBool ,
6062}
61- struct CountedOwnedTasksInner < S : ' static > {
62- list : CountedLinkedList < Task < S > , <Task < S > as Link >:: Target > ,
63- closed : bool ,
64- }
63+
64+ type List < S > = sharded_list:: ShardedList < Task < S > , <Task < S > as Link >:: Target > ;
65+
6566pub ( crate ) struct LocalOwnedTasks < S : ' static > {
6667 inner : UnsafeCell < OwnedTasksInner < S > > ,
6768 pub ( crate ) id : NonZeroU64 ,
6869 _not_send_or_sync : PhantomData < * const ( ) > ,
6970}
71+
7072struct OwnedTasksInner < S : ' static > {
7173 list : LinkedList < Task < S > , <Task < S > as Link >:: Target > ,
7274 closed : bool ,
7375}
7476
7577impl < S : ' static > OwnedTasks < S > {
76- pub ( crate ) fn new ( ) -> Self {
78+ pub ( crate ) fn new ( num_cores : usize ) -> Self {
79+ let shard_size = Self :: gen_shared_list_size ( num_cores) ;
7780 Self {
78- inner : Mutex :: new ( CountedOwnedTasksInner {
79- list : CountedLinkedList :: new ( ) ,
80- closed : false ,
81- } ) ,
81+ list : List :: new ( shard_size) ,
82+ closed : AtomicBool :: new ( false ) ,
8283 id : get_next_id ( ) ,
8384 }
8485 }
@@ -112,24 +113,23 @@ impl<S: 'static> OwnedTasks<S> {
112113 task. header ( ) . set_owner_id ( self . id ) ;
113114 }
114115
115- let mut lock = self . inner . lock ( ) ;
116- if lock. closed {
117- drop ( lock) ;
118- drop ( notified) ;
116+ let shard = self . list . lock_shard ( & task) ;
117+ // Check the closed flag in the lock for ensuring all that tasks
118+ // will shut down after the OwnedTasks has been closed.
119+ if self . closed . load ( Ordering :: Acquire ) {
120+ drop ( shard) ;
119121 task. shutdown ( ) ;
120- None
121- } else {
122- lock. list . push_front ( task) ;
123- Some ( notified)
122+ return None ;
124123 }
124+ shard. push ( task) ;
125+ Some ( notified)
125126 }
126127
127128 /// Asserts that the given task is owned by this OwnedTasks and convert it to
128129 /// a LocalNotified, giving the thread permission to poll this task.
129130 #[ inline]
130131 pub ( crate ) fn assert_owner ( & self , task : Notified < S > ) -> LocalNotified < S > {
131132 debug_assert_eq ! ( task. header( ) . get_owner_id( ) , Some ( self . id) ) ;
132-
133133 // safety: All tasks bound to this OwnedTasks are Send, so it is safe
134134 // to poll it on this thread no matter what thread we are on.
135135 LocalNotified {
@@ -140,34 +140,34 @@ impl<S: 'static> OwnedTasks<S> {
140140
141141 /// Shuts down all tasks in the collection. This call also closes the
142142 /// collection, preventing new items from being added.
143- pub ( crate ) fn close_and_shutdown_all ( & self )
143+ ///
144+ /// The parameter start determines which shard this method will start at.
145+ /// Using different values for each worker thread reduces contention.
146+ pub ( crate ) fn close_and_shutdown_all ( & self , start : usize )
144147 where
145148 S : Schedule ,
146149 {
147- // The first iteration of the loop was unrolled so it can set the
148- // closed bool.
149- let first_task = {
150- let mut lock = self . inner . lock ( ) ;
151- lock. closed = true ;
152- lock. list . pop_back ( )
153- } ;
154- match first_task {
155- Some ( task) => task. shutdown ( ) ,
156- None => return ,
150+ self . closed . store ( true , Ordering :: Release ) ;
151+ for i in start..self . get_shard_size ( ) + start {
152+ loop {
153+ let task = self . list . pop_back ( i) ;
154+ match task {
155+ Some ( task) => {
156+ task. shutdown ( ) ;
157+ }
158+ None => break ,
159+ }
160+ }
157161 }
162+ }
158163
159- loop {
160- let task = match self . inner . lock ( ) . list . pop_back ( ) {
161- Some ( task) => task,
162- None => return ,
163- } ;
164-
165- task. shutdown ( ) ;
166- }
164+ #[ inline]
165+ pub ( crate ) fn get_shard_size ( & self ) -> usize {
166+ self . list . shard_size ( )
167167 }
168168
169169 pub ( crate ) fn active_tasks_count ( & self ) -> usize {
170- self . inner . lock ( ) . list . count ( )
170+ self . list . len ( )
171171 }
172172
173173 pub ( crate ) fn remove ( & self , task : & Task < S > ) -> Option < Task < S > > {
@@ -179,11 +179,27 @@ impl<S: 'static> OwnedTasks<S> {
179179
180180 // safety: We just checked that the provided task is not in some other
181181 // linked list.
182- unsafe { self . inner . lock ( ) . list . remove ( task. header_ptr ( ) ) }
182+ unsafe { self . list . remove ( task. header_ptr ( ) ) }
183183 }
184184
185185 pub ( crate ) fn is_empty ( & self ) -> bool {
186- self . inner . lock ( ) . list . is_empty ( )
186+ self . list . is_empty ( )
187+ }
188+
189+ /// Generates the size of the sharded list based on the number of worker threads.
190+ ///
191+ /// The sharded lock design can effectively alleviate
192+ /// lock contention performance problems caused by high concurrency.
193+ ///
194+ /// However, as the number of shards increases, the memory continuity between
195+ /// nodes in the intrusive linked list will diminish. Furthermore,
196+ /// the construction time of the sharded list will also increase with a higher number of shards.
197+ ///
198+ /// Due to the above reasons, we set a maximum value for the shared list size,
199+ /// denoted as `MAX_SHARED_LIST_SIZE`.
200+ fn gen_shared_list_size ( num_cores : usize ) -> usize {
201+ const MAX_SHARED_LIST_SIZE : usize = 1 << 16 ;
202+ usize:: min ( MAX_SHARED_LIST_SIZE , num_cores. next_power_of_two ( ) * 4 )
187203 }
188204}
189205
@@ -192,9 +208,9 @@ cfg_taskdump! {
192208 /// Locks the tasks, and calls `f` on an iterator over them.
193209 pub ( crate ) fn for_each<F >( & self , f: F )
194210 where
195- F : FnMut ( & Task <S >)
211+ F : FnMut ( & Task <S >) ,
196212 {
197- self . inner . lock ( ) . list. for_each( f)
213+ self . list. for_each( f) ;
198214 }
199215 }
200216}
0 commit comments