def getter(array: Array, cv: Condition): """ Awaits notification through a Condition object (condition variable) that a value is available in array. Reads the value and prints it. :param array: a multiprocessing Array of integers of size 1 from which to read a value :param cv: a Condition object (a condition variable) to allow the value to be read only when it is ready """ print('In getter.') with cv: # wait_for takes a _predicate_, which is a boolean-valued function. # A lambda expression creates an anonymous function that returns the result of evaluating the body. # This invocation causes the process to block until the value in a is set. cv.wait_for(lambda: array[0] != 0) print(f'Got {array[0]}.')
class CountDownLatch(object): """A synchronization aid that allows one or more processes to wait until a set of operations being performed in other processes completes. A CountDownLatch is initialized with a given count. The wait method blocks until the current count reaches zero due to invocations of the count_down() method, after which all waiting processes are released and any subsequent invocations of wait return immediately. The count can be reset, but one need to be 100% sure no other processes are waiting or counting down during reset. """ def __init__(self, count: int = 1, lock=None): self.__count = Value('i', count, lock=True if lock is None else lock) self.__lock = Condition(lock) def reset(self, count): self.__lock.acquire() self.__count.value = count self.__lock.release() def count_down(self): self.__lock.acquire() self.__count.value -= 1 result = self.__count.value if self.__count.value <= 0: self.__lock.notify_all() self.__lock.release() return result def wait(self, timeout=None): self.__lock.acquire() result = self.__lock.wait_for(lambda: self.__count.value <= 0, timeout) self.__lock.release() return result
class ConditionVariableDemo: """ Demonstrates the use of condition variables within a class. """ def __init__(self): self.array = Array('i', 1) self.cv = Condition() def run(self): """ Creates, runs, and joins a setter and a getter process. """ p0 = Process(target=self.setter) p1 = Process(target=self.getter) p0.start() p1.start() p0.join() p1.join() def setter(self): """ Sets the value of the first (and only) index position in a exclusively and notifies the process awaiting this action that a valid value is available. :param a: a multiprocessing Array of integers of size 1 to which to write a value :param cv: a Condition object (a condition variable) to allow the value to be read only when it is ready """ with self.cv: print('In setter.') print('Before setting:', self.array[0]) self.array[0] = 43 print('After setting:', self.array[0]) self.cv.notify() def getter(self): """ Awaits notification through a Condition object (condition variable) that a value is available in a. Reads the value and prints it. :param a: a multiprocessing Array of integers of size 1 from which to read a value :param cv: a Condition object (a condition variable) to allow the value to be read only when it is ready """ print('In getter.') with self.cv: self.cv.wait_for(lambda: self.array[0] != 0) print(f'Got {self.array[0]}.')
def fork_child(request, comms): val = Value('i', 0) lock = RLock() cond = Condition(lock) pid = os.fork() if pid: # parent with lock: val.value = 1 cond.notify_all() cond.wait_for(lambda: val.value == 2) return pid else: # child # noinspection PyBroadException try: handler = CaptureHTTPHandler(request, comms) with lock: cond.wait_for(lambda: val.value == 1) val.value = 2 cond.notify_all() handler.serve() except Exception: request.server.handle_error(request.req, request.client_address) with lock: cond.wait_for(lambda: val.value == 1) val.value = 2 cond.notify_all() finally: request.server.shutdown_request(request.req) comms.close() # child does not exit normally import signal os.kill(os.getpid(), signal.SIGKILL)
class HogwildWorld(World): """Creates a separate world for each thread (process). Maintains a few shared objects to keep track of state: - A Semaphore which represents queued examples to be processed. Every call of parley increments this counter; every time a Process claims an example, it decrements this counter. - A Condition variable which notifies when there are no more queued examples. - A boolean Value which represents whether the inner worlds should shutdown. - An integer Value which contains the number of unprocessed examples queued (acquiring the semaphore only claims them--this counter is decremented once the processing is complete). """ def __init__(self, world_class, opt, agents): self.inner_world = world_class(opt, agents) self.queued_items = Semaphore(0) # counts num exs to be processed self.epochDone = Condition() # notifies when exs are finished self.terminate = Value('b', False) # tells threads when to shut down self.cnt = Value('i', 0) # number of exs that remain to be processed self.threads = [] for i in range(opt['numthreads']): self.threads.append( HogwildProcess(i, world_class, opt, agents, self.queued_items, self.epochDone, self.terminate, self.cnt)) for t in self.threads: t.start() def __iter__(self): raise NotImplementedError('Iteration not available in hogwild.') def display(self): self.shutdown() raise NotImplementedError( 'Hogwild does not support displaying in-run' + ' task data. Use `--numthreads 1`.') def episode_done(self): return False def parley(self): """Queue one item to be processed.""" with self.cnt.get_lock(): self.cnt.value += 1 self.queued_items.release() def getID(self): return self.inner_world.getID() def report(self): return self.inner_world.report() def save(self): self.inner_world.save() def synchronize(self): """Sync barrier: will wait until all queued examples are processed.""" with self.epochDone: self.epochDone.wait_for(lambda: self.cnt.value == 0) def shutdown(self): """Set shutdown flag and wake threads up to close themselves""" # set shutdown flag with self.terminate.get_lock(): self.terminate.value = True # wake up each thread by queueing fake examples for _ in self.threads: self.queued_items.release() # wait for threads to close for t in self.threads: t.join()
class StateLatch(object): """A synchronization aid that allows one or more processes to wait for state change until a set of operations being performed in other processes completes. A StateLatch is initialized with a given state. The wait and wait_for methods block until the current state changes to the desired state due to invocations of the next() method, after which all waiting processes are released and any subsequent invocations of wait or wait_for return immediately. While changing state one can set the counter of the next state. The next state won't take effect due to invocations of the next() method until the counter reaches zero. This ensures the requested number of processes completed their work when state actually changes. The counter of next state can be amended without state change using set_next method, but one need to be 100% sure no other processes are waiting or changing state at the moment. """ def __init__(self, state=State.READY, lock: RLock = None): self.__state = Value('i', state, lock=True if lock is None else lock) self.__lock = Condition(lock) self.__next_state_count_down = CountDownLatch(0, lock) self.__next_state_count_down_max = Value( 'i', 0, lock=True if lock is None else lock) def set_next(self, next_state_count_down): self.__lock.acquire() self.__next_state_count_down.reset(next_state_count_down) self.__next_state_count_down_max.value = 0 self.__lock.release() def next(self, next_state_count_down: int = 0): self.__lock.acquire() old = State(self.__state.value) self.__next_state_count_down_max.value = max( self.__next_state_count_down_max.value, next_state_count_down) if self.__next_state_count_down.wait(0) or \ self.__next_state_count_down.count_down() == 0: self.__state.value = State.next(self.__state.value) self.__next_state_count_down.reset( self.__next_state_count_down_max.value) self.__next_state_count_down_max.value = 0 new = State(self.__state.value) self.__lock.notify_all() self.__lock.release() return old, new def wait(self, state, timeout=None): self.__lock.acquire() result = self.__lock.wait_for(lambda: self.__state.value == state, timeout) self.__lock.release() return result def wait_for(self, state, predicate, timeout=None): """Wait for the desired state or until a condition evaluates to true. predicate must be a callable with the result interpreted as a boolean value. A timeout may be provided giving the maximum time to wait. While waiting the predicate is being checked every second. :param state: state to wait for :param predicate: callable function with the result interpreted as a boolean value :param timeout: the maximum time to wait :return: the last return value of the predicate or False if the method timed out """ self.__lock.acquire() try: result = self.__state.value == state or predicate() if result: return result end_time = None if timeout is None else monotonic() + timeout wait_time = 1 while not result: if end_time is not None: wait_time = min(end_time - monotonic(), 1) if wait_time <= 0: break result = self.__lock.wait_for( lambda: self.__state.value == state, wait_time) or predicate() return result finally: self.__lock.release() @property def state(self): return State(self.__state.value)
class HogwildWorld(World): """Creates a separate world for each thread (process). Maintains a few shared objects to keep track of state: - A Semaphore which represents queued examples to be processed. Every call of parley increments this counter; every time a Process claims an example, it decrements this counter. - A Condition variable which notifies when there are no more queued examples. - A boolean Value which represents whether the inner worlds should shutdown. - An integer Value which contains the number of unprocessed examples queued (acquiring the semaphore only claims them--this counter is decremented once the processing is complete). """ def __init__(self, world_class, opt, agents): self.inner_world = world_class(opt, agents) self.queued_items = Semaphore(0) # counts num exs to be processed self.epochDone = Condition() # notifies when exs are finished self.terminate = Value('b', False) # tells threads when to shut down self.cnt = Value('i', 0) # number of exs that remain to be processed self.threads = [] for i in range(opt['numthreads']): self.threads.append(HogwildProcess(i, world_class, opt, agents, self.queued_items, self.epochDone, self.terminate, self.cnt)) for t in self.threads: t.start() def __iter__(self): raise NotImplementedError('Iteration not available in hogwild.') def display(self): self.shutdown() raise NotImplementedError('Hogwild does not support displaying in-run' + ' task data. Use `--numthreads 1`.') def episode_done(self): return False def parley(self): """Queue one item to be processed.""" with self.cnt.get_lock(): self.cnt.value += 1 self.queued_items.release() def getID(self): return self.inner_world.getID() def report(self): return self.inner_world.report() def save_agents(self): self.inner_world.save_agents() def synchronize(self): """Sync barrier: will wait until all queued examples are processed.""" with self.epochDone: self.epochDone.wait_for(lambda: self.cnt.value == 0) def shutdown(self): """Set shutdown flag and wake threads up to close themselves""" # set shutdown flag with self.terminate.get_lock(): self.terminate.value = True # wake up each thread by queueing fake examples for _ in self.threads: self.queued_items.release() # wait for threads to close for t in self.threads: t.join()