Threading Event Object In Python

February 28, 2022 Python Threading

You can use an Event Object in Python via the threading.Event class.

In this tutorial you will discover how to use an event object in Python.

Let's get started.

Need for an Event Object

A thread is a thread of execution in a computer program.

Every Python program has at least one thread of execution called the main thread. Both processes and threads are created and managed by the underlying operating system.

Sometimes we may need to create additional threads in our program in order to execute code concurrently.

Python provides the ability to create and manage new threads via the threading module and the threading.Thread class.

You can learn more about Python threads in the guude:

In concurrent programming in threads, sometimes we need to coordinate threads with a boolean variable. This might be to trigger an action or signal some result.

This could be achieved with a mutual exclusion lock (mutex) and a boolean variable, but provides no way for threads to wait for the variable to be set True.

Instead, this can be achieved using an event object.

What is an event object and how can we use it in Python?

How to Use an Event Object

Python provides an event object via the threading.Event class.

An event is a simple concurrency primitive that allows communication between threads.

A threading.Event object wraps a boolean variable that can either be "set" (True) or "not set" (False). Threads sharing the event instance can check if the event is set, set the event, clear the event (make it not set), or wait for the event to be set.

The threading.Event provides an easy way to share a boolean variable between threads that can act as a trigger for an action.

This is one of the simplest mechanisms for communication between threads: one thread signals an event and other threads wait for it.

-- Event Objects, threading — Thread-based parallelism

First, an event object must be created and the event will be in the "not set" state.

...
# create an instance of an event
event = threading.Event()

Once created we can check if the event has been set via the is_set() function which will return True if the event is set, or False otherwise.

For example:

...
# check if the event is set
if event.is_set():
	# do something...

The event can be set via the set() function. Any threads waiting on the event to be set will be notified.

For example:

...
# set the event
event.set()

The event can be marked as "not set" (whether it is currently set or not) via the clear() function.

...
# mark the event as not set
event.clear()

Finally, threads can wait for the event to set via the wait() function. Calling this function will block until the event is marked as set (e.g. another thread calling the set() function). If the event is already set, the wait() function will return immediately.

...
# wait for the event to be set
event.wait()

From reviewing the source code for threading.Event, waiting threads are only notified when the set() function is called, not when the clear() function is called.

A "timeout" argument can be passed to the wait() function which will limit how long a thread is willing to wait in seconds for the event to be marked as set.

The wait() function will return True if the event was set while waiting, otherwise a value of False returned indicates that the event was not set and the call timed-out.

...
# wait for the event to be set with a timeout
event.wait(timeout=10)

Now that we know how to use a threading.Event, let's look at a worked example.

Example of Using an Event Object

We can explore how to use a threading.Event object.

In this example we will create a suite of threads that each will perform some processing and report a message. All threads will use an event to wait to be set before starting their work. The main thread will set the event and trigger the processing in all threads.

First, we can define a target task function that takes the shared threading.Event instance and a unique integer to identify the thread.

# target task function
def task(event, number):
	# ...

Next, the function will wait for the event to be set before starting the processing work.

...
# wait for the event to be set
event.wait()

Once triggered, the thread will generate a random number, block for a moment and report a message.

...
# begin processing
value = random()
sleep(value)
print(f'Thread {number} got {value}')

Tying this together, the complete target task function is listed below.

# target task function
def task(event, number):
    # wait for the event to be set
    event.wait()
    # begin processing
    value = random()
    sleep(value)
    print(f'Thread {number} got {value}')

The main thread will first create the shared threading.Event instance, which will be in the "not set" state by default.

...
# create a shared event object
event = Event()

Next, we can start five new threads specifying the target task() function with the event object and a unique integer as arguments.

...
# create a suite of threads
for i in range(5):
    thread = Thread(target=task, args=(event, i))
    thread.start()

Finally, the main thread will block for a moment, then trigger the processing in all of the threads via the event object.

...
# block for a moment
print('Main thread blocking...')
sleep(2)
# start processing in all threads
event.set()

Tying this all together, the complete example is listed below.

# SuperFastPython.com
# example of using an event object
from time import sleep
from random import random
from threading import Thread
from threading import Event

# target task function
def task(event, number):
    # wait for the event to be set
    event.wait()
    # begin processing
    value = random()
    sleep(value)
    print(f'Thread {number} got {value}')

# create a shared event object
event = Event()
# create a suite of threads
for i in range(5):
    thread = Thread(target=task, args=(event, i))
    thread.start()
# block for a moment
print('Main thread blocking...')
sleep(2)
# start processing in all threads
event.set()
# wait for all the threads to finish...

Running the example first creates and starts five threads. Each thread waits on the event before it starts processing.

The main thread blocks for a moment, allowing all threads to get started and start waiting on the event. The main thread then sets the event. This triggers all five threads that perform their processing and report a message.

Note, your specific results will differ given the use of random numbers.

Main thread blocking...
Thread 2 got 0.12310258010408259
Thread 4 got 0.2951602689055347
Thread 3 got 0.2995528547475702
Thread 0 got 0.6661975710559368
Thread 1 got 0.6687449622581129

Takeaways

You now know how to use a threading.Event Object in Python



If you enjoyed this tutorial, you will love my book: Python Threading Jump-Start. It covers everything you need to master the topic with hands-on examples and clear explanations.