- File:
-
- 1 edited
-
libcfa/src/concurrency/future.hfa (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
libcfa/src/concurrency/future.hfa
r70a4ed5 r8f1a99e 5 5 // file "LICENCE" distributed with Cforall. 6 6 // 7 // concurrency/future.hfa --7 // io/types.hfa -- 8 8 // 9 // Author : Thierry Delisle & Peiran Hong & Colby Parsons9 // Author : Thierry Delisle & Peiran Hong 10 10 // Created On : Wed Jan 06 17:33:18 2021 11 11 // Last Modified By : … … 18 18 #include "bits/locks.hfa" 19 19 #include "monitor.hfa" 20 #include "select.hfa"21 #include "locks.hfa"22 23 //----------------------------------------------------------------------------24 // future25 // I don't use future_t here since I need to use a lock for this future26 // since it supports multiple consumers27 // future_t is lockfree and uses atomics which aren't needed given we use locks here28 forall( T ) {29 // enum { FUTURE_EMPTY = 0, FUTURE_FULFILLED = 1 }; // Enums seem to be broken so feel free to add this back afterwards30 31 // temporary enum replacement32 const int FUTURE_EMPTY = 0;33 const int FUTURE_FULFILLED = 1;34 35 struct future {36 int state;37 T result;38 dlist( select_node ) waiters;39 futex_mutex lock;40 };41 42 struct future_node {43 inline select_node;44 T * my_result;45 };46 47 static inline {48 49 void ?{}( future_node(T) & this, thread$ * blocked_thread, T * my_result ) {50 ((select_node &)this){ blocked_thread };51 this.my_result = my_result;52 }53 54 void ?{}( future(T) & this ) {55 this.waiters{};56 this.state = FUTURE_EMPTY;57 this.lock{};58 }59 60 // Reset future back to original state61 void reset( future(T) & this ) with(this)62 {63 lock( lock );64 if( ! waiters`isEmpty )65 abort("Attempting to reset a future with blocked waiters");66 state = FUTURE_EMPTY;67 unlock( lock );68 }69 70 // check if the future is available71 // currently no mutual exclusion because I can't see when you need this call to be synchronous or protected72 bool available( future(T) & this ) { return __atomic_load_n( &this.state, __ATOMIC_RELAXED ); }73 74 75 // memcpy wrapper to help copy values76 void copy_T( T & from, T & to ) {77 memcpy((void *)&to, (void *)&from, sizeof(T));78 }79 80 // internal helper to signal waiters off of the future81 void _internal_flush( future(T) & this ) with(this) {82 while( ! waiters`isEmpty ) {83 if ( !__handle_waituntil_OR( waiters ) ) // handle special waituntil OR case84 break; // if handle_OR returns false then waiters is empty so break85 select_node &s = try_pop_front( waiters );86 87 if ( s.clause_status == 0p ) // poke in result so that woken threads do not need to reacquire any locks88 copy_T( result, *(((future_node(T) &)s).my_result) );89 90 wake_one( waiters, s );91 }92 }93 94 // Fulfil the future, returns whether or not someone was unblocked95 bool fulfil( future(T) & this, T val ) with(this) {96 lock( lock );97 if( state != FUTURE_EMPTY )98 abort("Attempting to fulfil a future that has already been fulfilled");99 100 copy_T( val, result );101 102 bool ret_val = ! waiters`isEmpty;103 state = FUTURE_FULFILLED;104 _internal_flush( this );105 unlock( lock );106 return ret_val;107 }108 109 // Wait for the future to be fulfilled110 // Also return whether the thread had to block or not111 [T, bool] get( future(T) & this ) with( this ) {112 lock( lock );113 T ret_val;114 if( state == FUTURE_FULFILLED ) {115 copy_T( result, ret_val );116 unlock( lock );117 return [ret_val, false];118 }119 120 future_node(T) node = { active_thread(), &ret_val };121 insert_last( waiters, ((select_node &)node) );122 unlock( lock );123 park( );124 125 return [ret_val, true];126 }127 128 // Wait for the future to be fulfilled129 T get( future(T) & this ) {130 [T, bool] tt;131 tt = get(this);132 return tt.0;133 }134 135 // Gets value if it is available and returns [ val, true ]136 // otherwise returns [ default_val, false]137 // will not block138 [T, bool] try_get( future(T) & this ) with(this) {139 lock( lock );140 T ret_val;141 if( state == FUTURE_FULFILLED ) {142 copy_T( result, ret_val );143 unlock( lock );144 return [ret_val, true];145 }146 unlock( lock );147 148 return [ret_val, false];149 }150 151 bool register_select( future(T) & this, select_node & s ) with(this) {152 lock( lock );153 154 // check if we can complete operation. If so race to establish winner in special OR case155 if ( !s.park_counter && state != FUTURE_EMPTY ) {156 if ( !__make_select_node_available( s ) ) { // we didn't win the race so give up on registering157 unlock( lock );158 return false;159 }160 }161 162 // future not ready -> insert select node and return163 if( state == FUTURE_EMPTY ) {164 insert_last( waiters, s );165 unlock( lock );166 return false;167 }168 169 __make_select_node_available( s );170 unlock( lock );171 return true;172 }173 174 bool unregister_select( future(T) & this, select_node & s ) with(this) {175 if ( ! s`isListed ) return false;176 lock( lock );177 if ( s`isListed ) remove( s );178 unlock( lock );179 return false;180 }181 182 void on_selected( future(T) & this, select_node & node ) {}183 }184 }185 186 //--------------------------------------------------------------------------------------------------------187 // These futures below do not support select statements so they may not have as many features as 'future'188 // however the 'single_future' is cheap and cheerful and is most likely more performant than 'future'189 // since it uses raw atomics and no locks190 //191 // As far as 'multi_future' goes I can't see many use cases as it will be less performant than 'future'192 // since it is monitor based and also is not compatible with select statements193 //--------------------------------------------------------------------------------------------------------194 20 195 21 forall( T ) { 196 struct single_future {22 struct future { 197 23 inline future_t; 198 24 T result; … … 201 27 static inline { 202 28 // Reset future back to original state 203 void reset( single_future(T) & this) { reset( (future_t&)this ); }29 void reset(future(T) & this) { reset( (future_t&)this ); } 204 30 205 31 // check if the future is available 206 bool available( single_future(T) & this ) { return available( (future_t&)this ); }32 bool available( future(T) & this ) { return available( (future_t&)this ); } 207 33 208 34 // Mark the future as abandoned, meaning it will be deleted by the server 209 35 // This doesn't work beause of the potential need for a destructor 210 void abandon( single_future(T) & this );36 void abandon( future(T) & this ); 211 37 212 38 // Fulfil the future, returns whether or not someone was unblocked 213 thread$ * fulfil( single_future(T) & this, T result ) {39 thread$ * fulfil( future(T) & this, T result ) { 214 40 this.result = result; 215 41 return fulfil( (future_t&)this ); … … 218 44 // Wait for the future to be fulfilled 219 45 // Also return whether the thread had to block or not 220 [T, bool] wait( single_future(T) & this ) {46 [T, bool] wait( future(T) & this ) { 221 47 bool r = wait( (future_t&)this ); 222 48 return [this.result, r]; … … 224 50 225 51 // Wait for the future to be fulfilled 226 T wait( single_future(T) & this ) {52 T wait( future(T) & this ) { 227 53 [T, bool] tt; 228 54 tt = wait(this);
Note:
See TracChangeset
for help on using the changeset viewer.