Pseudo-code from article of the above name, ACM TOCS, February 1997. Leonidas I. Kontothanassis, Robert W. Wisniewski, and Michael L. Scott.
type context_block = record state : (preempted, preemptable, unpreemptable_self, unpreemptable_other) warning : Boolean ... type partition_block = record num_processors, generation : integer processes_on_processor : array [MAX_PROCESSORS] of integer processor_ids : array [MAX_PROCESSES] of integer ...
type multi_flag = (not_yet, can_go, got_it, lost_it, ack, nack) type qnode = record next, prev : ^qnode next_done : Boolean status : multi_flag type lock = ^qnode private cb : ^context_block procedure acquire_lock (L : ^lock, I : ^qnode) loop I->next := nil cb->state := unpreemptable_self I->prev := fetch_and_store (L, I) if I->prev = nil return I->status := not_yet I->prev->next := I repeat cb->state := preemptable if cb->warning yield // kernel wanted to preempt me cb->state := unpreemptable_self until I->status != not_yet // spin val : multi_flag := fetch_and_store (I->status, got_it) if val = can_go I->prev->next_done := true // tell prev I'm done with its qnode repeat until I->status = ack // let prev finish using my qnode return while val != nack val := I->status // wait until qnode no longer needed procedure release_lock (L : ^lock, I: ^qnode) if I->next = nil // no known successor if compare_and_store (L, I, nil) goto rtn repeat while I->next = nil // spin I->next_done := false loop I->next->status := can_go for i in 1..TIMEOUT // spin if I->next_done I->next->status := ack; goto rtn if fetch_and_store (I->next->status, lost_it) = got_it // oh! successor was awake after all repeat until I->next_done I->next->status := ack; goto rtn succ : ^qnode := I->next->next // successor was asleep if succ = nil if compare_and_store (L, I->next, nil) I->next->status := nack; goto rtn repeat while (succ := I->next->next) = nil // spin; non-local I->next->status := nack I->next := succ; succ->prev := I rtn: cb->state := preemptable if cb->warning yield // kernel wanted to preempt me
NB: This code incorporates a bug fix due to Hiroaki Takada and another due to Injong Rhee and Chi-Yung Lee.
type qnode = record self : ^context_block next : ^qnode status : (waiting, success, failure) type lock = ^qnode private cb : ^context_block; procedure acquire_lock (L : ^lock, I : ^qnode) repeat I->next := nil I->self := cb cb->state := unpreemptable_self pred : ^qnode := fetch_and_store (L, I) if pred = nil return I->status := waiting pred->next := I (void) compare_and_store (&cb->state, unpreemptable_self, preemptable) repeat while I->status = waiting // spin until I->status = success procedure release_lock (L : ^lock, I : ^qnode) shadow : ^qnode := I candidate : ^qnode := I->next loop if candidate = nil if compare_and_store (L, shadow, nil) shadow->status := failure // may set our own flag, // but that's ok exit loop // no one waiting for lock repeat while shadow->next = nil // spin; probably non-local candidate := shadow->next shadow->status := failure // outside the if // order of following checks is important if compare_and_store (&candidate->self->state, unpreemptable_self, unpreemptable_other) or compare_and_store (&candidate->self->state, preemptable, unpreemptable_other) candidate->status := success exit loop // else candidate seems to be preempted shadow := candidate // move down queue candidate := shadow->next cb->state := preemptable if cb->warning yield
type t_lock = record next_ticket, now_serving, ack_flag, done_flag : unsigned integer private cb : ^context_block procedure acquire_lock (L : ^t_lock) restart: cb->state := unpreemptable_self my_ticket : integer := fetch_and_increment (&L->next_ticket) // overflow is benign while my_ticket != L->now_serving cb->state := preemptable if cb->warning yield cb->state := unpreemptable_self if (my_ticket - L->now_serving) > MAX_PROCESSES // I've been passed up (overflow is benign) goto restart for i in 1..((my_ticket - L->now_serving) * SPIN_FACTOR) // spin if L->done_flag = my_ticket // optimization; releaser definitely isn't waiting for acknowledgment return if !compare_and_store (&L->ack_flag, my_ticket, my_ticket-MAX_PROCESSES) goto restart procedure release_lock (L : ^t_lock) retry: new_ticket : integer := L->ack_flag := L->now_serving + 1 L->now_serving := new_ticket if L->next_ticket = L->now_serving // nobody waiting goto rtn for i : integer in 1..TIMEOUT if L->ack_flag = new_ticket - MAX_PROCESSES goto rtn // I timed out if compare_and_store (&L->ack_flag, new_ticket, new_ticket-MAX_PROCESSES) // ticket successfully rescinded goto retry rtn: L->done_flag := new_ticket // optimization; avoids need for acknowledgment in no-contention case cb->state := preemptable if cb->warning yield
NB: This code incorporates a bug fix due to Injong Rhee and Chi-Yung Lee.
type rw_qnode = record self : ^context_block state : (reader, active_reader, writer) spin_flag : (waiting, success, failure) next, prev : ^rw_qnode exc_lock : exclusive_lock type rw_lock = ^rw_qnode private cb : ^context_block procedure writer_lock (L : ^rw_lock, I : ^rw_qnode) I->self := cb repeat cb->state := unpreemptable_self I->state := writer I->spin_flag := waiting I->next := nil pred : ^rw_qnode := fetch_and_store (L, I) if pred != nil pred->next := I (void) compare_and_store (&cb->state, unpreemptable_self, preemptable) repeat while I->spin_flag = waiting // spin else return until I->spin_flag = success procedure writer_unlock (L: ^rw_lock, I: ^rw_qnode) shadow : ^rw_qnode := I candidate : ^rw_qnode := I->next loop if candidate = nil if compare_and_store (L, shadow, nil) shadow-&ft;spin_flag := failure // may set our own flag, // but that's ok exit loop // no one waiting for lock repeat while shadow->next = nil // spin; probably non-local candidate := shadow->next shadow->spin_flag := failure; // outside the if // order of following checks is important if compare_and_store (&candidate->self->state, unpreemptable_self, unpreemptable_other) or compare_and_store (&candidate->self->state, preemptable, unpreemptable_other) candidate->prev := nil candidate->spin_flag := success exit loop // else candidate seems to be preempted shadow := candidate // move down queue candidate := shadow->next shadow->spin_flag := failure cb->state := preemptable if cb->warning yield procedure reader_lock (L : ^rw_lock, I : ^rw_qnode) I->self := cb exc_lock (I) repeat cb->state := unpreemptable_self I->next := I->prev := nil I->state := reader I->spin_flag := waiting pred : ^rw_qnode := fetch_and_store (L, I) if pred = nil exit loop // leave repeat I->prev := pred pred->next := I if pred->state = active_reader exit loop // leave repeat compare_and_store (&cb->state, unpreemptable_self, preemptable) repeat while I->spin_flag = waiting until I->spin_flag = success I->state := active_reader candidate : ^rw_qnode := I->next loop if candidate = nil or candidate->state != reader exit loop // order of following checks is important if compare_and_store (&candidate->self->state, unpreemptable_self, unpreemptable_other) or compare_and_store (&candidate->self->state, preemptable, unpreemptable_other) candidate->spin_flag := success exit loop // else candidate seems to be preempted if candidate->next = nil I->next := nil if compare_and_store (L, candidate, I) // we are now tail of queue candidate->spin_flag := failure exit loop // else need to spin until successor establishes pointers repeat while candidate->next = nil // preempted candidate has a successor I->next := candidate->next candidate->next->prev := I candidate->spin_flag := failure candidate := I->next exc_unlock (I) procedure reader_unlock (L : ^rw_lock, I : ^rw_qnode) find_previous: pred : ^rw_qnode := I->prev if pred = nil goto no_previous while !exc_lock_conditional (pred) pred := I->prev if pred = nil goto no_previous if pred != I->prev exc_unlock (pred) goto find_previous exc_lock (I) pred->next := nil if I->next = nil if !compare_and_store (L, I, I->prev) repeat while I->next = nil // spin if I->next != nil I->next->prev := I->prev I->prev->next := I->next exc_unlock (pred) goto rtn no_previous: exc_lock (I) loop candidate : ^rw_qnode := I->next if candidate = nil if compare_and_store (L, I, nil) goto rtn repeat while I->next = nil // spin else if candidate->self->state = unpreemptable_other or compare_and_store (&candidate->self->state, unpreemptable_self, unpreemptable_other) or compare_and_store (&candidate->self->state, preemptable, unpreemptable_other) if (candidate->state = writer) candidate->prev := nil candidate->spin_flag := success else candidate->spin_flag := success candidate->prev := nil goto rtn // else candidate seems to be preempted if candidate->next = nil if compare_and_store (L, candidate, nil) // no one at tail of queue candidate->spin_flag := failure goto rtn repeat while candidate->next = nil // spin // preempted candidate has a successor I->next := candidate->next candidate->next->prev := I candidate->spin_flag := failure rtn: exc_unlock (I) cb->state := preemptable if cb->warning yield
shared global_sense, barrier_count, num_blocked : integer := 0, 0, 0 shared wakeup_sems : array [2] of semaphore := {0} shared mutex : lock private local_sense : integer := 0 procedure barrier () local_sense := 1 - local_sense count : integer := fetch_and_increment (&barrier_count) if count < NUM_PROCESSES - 1 for i : integer in 1..SWITCH_TIME if global_sense = local_sense return acquire_lock (mutex) if global_sense = local_sense release_lock (mutex) return num_blocked +:= 1 release_lock (mutex) P (wakeup_sem[local_sense]) else barrier_count := 0 acquire_lock (mutex) global_sense := 1 - global_sense // release spinning processes count := num_blocked num_blocked := 0 release_lock (mutex) for i in 1..count V (wakeup_sems[local_sense]) // release blocked processes
shared global_sense, barrier_count, num_blocked : integer := 0, 0, 0 shared wakeup_sems : array [2] of semaphore := {0} shared mutex : lock private local_sense : integer := 0 private spin_threshold : integer := SWITCH_TIME private episode_count : integer := 0 private episode_time : array [3] of integer := {SWITCH_TIME} procedure barrier () local_sense := 1 - local_sense count : integer := fetch_and_increment (&barrier_count) if count < NUM_PROCESSES - 1 now : integer := get_current_time () for i in 1..spin_threshold if global_sense = local_sense goto exit_barrier acquire_lock (mutex) if global_sense = local_sense release_lock (mutex) goto exit_barrier num_blocked +:= 1 release_lock (mutex) P (wakeup_sem[local_sense]) exit_barrier: episode_time[episode_count] := get_current_time () - now episode_count := (episode_count + 1) % 3 if average (episode_time) < SWITCH_TIME spin_threshold := min (SWITCH_TIME, spin_threshold + ADJUST) else spin_threshold := max (0, spin_threshold - ADJUST) else barrier_count := 0 acquire_lock (mutex) global_sense := 1 - global_sense // release spinning processes count := num_blocked num_blocked := 0 release_lock (mutex) for i in 1..count V (wakeup_sem[local_sense]) // release blocked processes
shared global_sense, barrier_count : integer := 0, 0 shared wakeup_sems : array [2] of semaphore := {0} shared partition : ^partition_block shared barrier_processors : array [2] of integer := {partition->num_processors} private local_sense : integer := 0 procedure barrier () local_sense := 1 - local_sense count : integer := fetch_and_increment (&barrier_count) if count + 1 %lt; NUM_PROCESSES if count + 1 >= NUM_PROCESSES - barrier_processors[local_sense] repeat until global_sense = local_sense // spin else P (wakeup_sem[local_sense]) else barrier_count := 0 barrier_processors[1-local_sense] := partition->num_processors global_sense := 1 - global_sense for i in 1..(NUM_PROCESSES - barrier_processors[local_sense]) V (wakeup_sem[local_sense])
type whole_and_parts = union whole : long parts: array [4] of byte type tree_node = record have_child : whole_and_parts child_not_ready : whole_and_parts := have_child parent_flag : ^byte dummy : byte // something harmless to point at type processor_info = record barrier_count : integer := 0 wakeup_sems : array [2] of semaphore := {0} generation : integer := 0 // used to synchronize reorganization shared processors : array [MAX_PROCESSORS] of processor_info shared nodes : array [MAX_PROCESSORS] of tree_node // have_child and parent_flag fields of individual nodes are initialized // as appropriate in the inter-processor tree; see code in reorganize () shared global_sense : integer := 0 shared partition : ^partition_block shared barrier_partition : partition_block := partition^ private local_sense : integer := 0 private cb : ^context_block private process_id : integer := // unique number in 0..NUM_PROCESSES-1 private my_processor : integer := partition->processor_ids[process_id] private my_generation : integer := 0 procedure barrier () local_sense := 1 - local_sense L : processor_info := &processors[my_processor] count : integer := fetch_and_increment (&L->barrier_count) if count + 1 %lt; barrier_partition.processes_on_processor[my_processor] // not the last process on the processor P (L->wakeup_sems[local_sense]) goto rtn // last process on this processor; wait for children on other processors my_node : ^tree_node := &nodes[my_processor] repeat while my_node->child_not_ready.whole != 0 // spin // barrier has been achieved my_node->child_not_ready.whole := my_node->have_child.whole my_node->parent_flag^ := 0 // notify parent if my_processor = 0 // root of inter-processor tree // copy partition information if necessary; loop ensures atomicity check : integer := barrier_partition.generation while check != partition->generation check := partition->generation barrier_partition := partition^ global_sense := local_sense // release spinning processes else repeat while global_sense != local_sense // spin L->barrier_count := 0 // reset for this processor only for i in 1..count V (L->wakeup_sems[local_sense]) // release blocked processes rtn: if my_generation != barrier_partition.generation reorganize () procedure reorganize () my_generation := barrier_partition.generation my_processor := barrier_partition.processor_ids[process_id] my_node : ^tree_node := &nodes[my_processor] for i in 0..process_id-1 if barrier_partition.processor_ids[i] = my_processor // I'm not the representative of my processor repeat until processors[my_processor].generation = my_generation // spin return for i in 0..3 my_node->havechild.parts[i] := (integer) ((my_processor*4 + i+1) %lt; barrier_partition.num_processors) my_node->childnotready.whole := my_node->havechild.whole if my_processor = 0 // root of inter-processor tree my_node->parentflag := &my_node->dummy processors[my_processor].generation := my_generation // signal children it is safe to proceed else parent_id : integer := (my_processor-1)/4 my_node->parentflag := &nodes[parent_id].childnotready.parts[(my_processor-1)%4] processors[my_processor].generation := my_generation repeat until processors[parent_id].generation = my_generation // spin