Problem:

Using Python’s Condition class is error-prone and difficult. For example, the following pseudo-code would be quite typical of its use:

 

condition = threading.Condition()

 

def sender():

     while alive():

          wait_for_my_data_from_hardware()

           with condition:

                send_data_to_receiver()

                condition.raise()

 

def receiver():

     while alive():

          with condition:

                 condition.wait()

                 receive_all_data_from_sender()

                 process_data()

 

Main code will then run sender() and receiver() in separate threads.

 

(I know that in a simple case like this, I could just use a Queue. I’ve been working with code that needs to service several events, where polling queues would introduce overhead and latency so a Condition must be used, but I’ve simplified the above as much as possible to illustrate the issues even in this simple case).

 

There are two issues even with the above case:

1. Raising a condition only has any effect if the condition is already being waited upon; a condition does not “remember” that it has been raised. So in the example above, if the receiver starts after the sender, it will wait on the condition even though data is already available for it.  This issue can be solved by rearranging the code, waiting at the end of the loop rather than the start; but this is counter-intuitive and lots of examples online look like the above.

2. In the receiver, the condition has to be kept locked (be inside the “with” statement”) all the time. The lock is automatically released while the condition is being waited upon, but otherwise it must be locked, otherwise we risk missing the condition being raised. The result is that process_data() is called with the lock held – and so this will prevent the sender going round its loop. The sending thread becomes a slave to the processing thread. This may have a huge performance penalty, losing the advantage of loose coupling that threading should provide.

 

You might think that using an Event, rather than a Condition, would solve things, since an Event does “remember” that is has been set. But there is no atomic way to wait on an event and clear it afterwards.

 

Solution:

The solution is very simple: to create something that might be called a StickyCondition, or an AutoResetEvent. This is a Condition that does remember it has been raised; or if you prefer, it is an Event that resets itself after it has been waited upon.  The example then becomes:

 

auto_event = threading.AutoResetEvent()

 

def sender():

     while alive():

           wait_for_my_data_from_hardware()

           send_data_to_receiver()

           auto_event.set()

 

def receiver():

     while alive():

           auto_event.wait()

           receive_all_data_from_sender()

           process_data()

 

This solves both of the issues described: the receiver will not wait if data is already available, and the sender is not blocked by a lock that is being held by the receiver. The event doesn’t need to be locked at all, because of its internal memory.

 

Implementation is trivial, involving a boolean and a Condition in the cross-thread case. I presume it would be more involved for the cross-process case, but I can see no reason it would be impossible.

 

Please let me know if you think this would be useful.