Why using while control instead of if control in condition variables to synchronize different threads?

54 Views Asked by At

I have simple producer, consumer problem. While producer thread adding 10$ to global money variable, consumer thread will spend money. But there is one condition that money cannot be under 0. The example code given below:

from threading import Thread, Condition

condition = Condition()
money = 0

def producer():
    global money
    for _ in range(1000000):
        condition.acquire()
        money += 10
        condition.notify()
        condition.release()

def consumer():
    global money
    for _ in range(500000):
        condition.acquire()
        if money < 0:
        # while money < 0: why it's needed ? 
            condition.wait()
        money -= 20
        print("money in the bank after spend => ", money)
        condition.release()
        

if __name__ == "__main__":
 
    t1 = Thread(target=producer, args=())
    t2 = Thread(target=consumer, args=())

    t1.start()
    t2.start()

    t1.join()
    t2.join()

I saw many examples and they are using while money < 20: instead of my if control and I am confused right there. When thread waits with condition.wait() simply it release the condition lock and when consumer consume 10$ then notify the producer thread, the producer function starts working with acquiring the condition lock first. So there is no make sense to use while loop over there. Because every time the thread notifies, first step the notified thread try to acquire the lock and rest of the code continues working as normal flow. So there is no need to use while loop to control money variable's instant change, it will control the money variable anyways if the program running as expected. Am I right ? Or am I missing some point ?

1

There are 1 best solutions below

2
user2722968 On

The reason for using a while loop is hinted at in the documentation:

The while loop checking for the application’s condition is necessary because wait() can return after an arbitrary long time, and the condition which prompted the notify() call may no longer hold true.

If you think about your code with an if-condition:

with condition:
    if money < 0:
        condition.wait()
    money -= 20

... it is possible (at least as far as the implementation of the condition variable is concerned) that

  1. condition gets locked
  2. money is smaller than 0
  3. We condition.wait(); the lock is unlocked and "something" happens elsewhere
  4. "something" notifies all threads waiting on condition.
  5. Some other thread acquires the lock on condition and executes, modifying money, then unlocks condition
  6. Our thread returns from .wait() after acquiring the lock
  7. While the condition we waited for had become true while we hadn't acquired the lock, it also became untrue while we hadn't acquired the lock. We were notified about "a change" in the condition. But when we get around to inspect it, it is no longer true.

While your code in particular may not be susceptible to this problem, it is generally good code hygiene // muscle memory to design your code in such a way that it does not suffer from the problem described above: While .wait() executes, your thread might get notified, but the condition you are waiting for is no longer true by the time wait() returns.

The while-loop makes sure that if the condition became true and untrue while the lock was released, we simple try again.