Search code examples
pythonpython-multithreadingrace-conditionpython-all-function

Python race condition with joining threads via all function


Please see the below code snippet, which fails to get past the first run locally with Python 3.10.

When I try to join all the threads within the built-in all function, always one of the threads is still alive.

What is the issue here, why is a race condition happening?

import time
from threading import Thread

def expose_race_condition(run_index: int) -> None:
    threads: list[Thread] = [
        Thread(target=time.sleep, args=(1,)) for _ in range(10)
    ]
    for thread in threads:
        thread.start()

    # RACE CONDITION IS HERE
    assert not all(thread.join() for thread in threads)
    for thread_index, thread in enumerate(threads):
        # Uncommenting the below line defeats the race condition
        # assert not thread.join()
        assert (
            not thread.is_alive()
        ), f"Worker {thread_index} in run {run_index} was still alive."
    print(f"Completed run {run_index}")

if __name__ == "__main__":
    for i in range(10_000):
        expose_race_condition(i)

Solution

  • Sorry, I don't understand what you're trying to do. This line, for example:

       assert not all(thread.join() for thread in threads)
    

    just doesn't make sense. .join() always returns None, so it's the same as

       assert not all(None for thread in threads)
    

    except that it has the side effect of joining threads. all() short-circuits the first time it sees a False value, which None is, so only the first .join() is actually called. all(...) returns False, so not all(...) returns True, so the assert succeeds. It's like:

        threads[0].join()
        assert True
    

    Short course: any code paying any attention to the value thread.join() returns is probably flat-out broken, because None is the only value it will ever see.