Mutexes in Python

In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple threads.

import threading

class Container:
    def __init__(self):
        self.lock = threading.Lock()
        self.counters = {'a': 0, 'b': 0}

    def inc(self, name):
        with self.lock:
            self.counters[name] += 1

def do_increment(container, name, n):
    for _ in range(n):
        container.inc(name)

if __name__ == "__main__":
    c = Container()

    threads = []
    for _ in range(2):
        t = threading.Thread(target=do_increment, args=(c, 'a', 10000))
        threads.append(t)
        t.start()

    t = threading.Thread(target=do_increment, args=(c, 'b', 10000))
    threads.append(t)
    t.start()

    for t in threads:
        t.join()

    print(c.counters)

Container holds a dictionary of counters; since we want to update it concurrently from multiple threads, we add a Lock to synchronize access.

The inc method locks the mutex before accessing counters, and releases it at the end of the method using a context manager (with statement).

Note that in Python, we don’t need to explicitly initialize the lock as we do in the __init__ method.

In the main block, we create a Container instance and several threads to increment the counters concurrently. We use the threading module to create and manage threads.

The do_increment function increments a named counter in a loop. We create three threads: two incrementing counter ‘a’ and one incrementing counter ‘b’.

After starting all threads, we wait for them to finish using the join method.

Running the program shows that the counters are updated as expected:

$ python mutexes.py
{'a': 20000, 'b': 10000}

Next, we’ll look at implementing this same state management task using only threads and queues.