2

I am using multi-threading and try to use threading.Condition to synchronize between threads. The problem is like this:

I have a main thread. In some cases, it will do this:

if not is_flag_set():
    with condition:
        condition.wait(120)

The reason it does this is because it tries to give background thread some chances to do do_something() before it moves forward:

do_something()
set_flag()
with condition:
    condition.notifyAll()

The flag is a shared resource considered by both main thread and the background thread. I do add lock when threads try to access the flag. However, I think the method here does not solve the problem. Main thread may check the flag first and then the background thread can set the flag and do notifyAll. This will cause the main thread actually waste 120 seconds.

I want a method which can make sure the main thread will wait for background thread only if the do_something() from background thread is not finished.

Edit: Just realize I forgot the with statement.

2
  • As a side note: notifyAll has been deprecated since Python 2.6; use notify_all. But really, if you only have one waiter, why aren't you just using notify? Commented Apr 13, 2015 at 3:55
  • I simplified the problem here. Actually the background thread will probably notify more than 1 threads. Also, thanks for pointing out this! Commented Apr 13, 2015 at 3:59

1 Answer 1

5

I think you're missing the point of a Condition: you have to check the condition predicate/variable inside the condition object, not outside.

The while point of condition.wait() is that it will wait until it's been notified. And it will wait cheaply, not using CPU power and burning down your battery; it does absolutely nothing until it's notified. So, if you use them properly, they do exactly what you ask: "wait for background thread only if the do_something() from background thread is not finished."

But you have to use them right. On the main thread, do this:

with condition:
    condition.wait_for(is_flag_set)
    do_stuff()

Then, on the background thread, you notify it like this:

do_something()
with condition:
    set_flag()
    condition.notify()

I've removed the timeout for simplicity. If you want to make sure that it waits until the background thread does do_something(), or 2 minutes, whichever comes sooner:

with condition:
    if condition.wait_for(is_flag_set, 120):
        do_stuff_after_flag_set()
    else:
        do_stuff_after_timeout()

Now, you're guaranteed that either do_stuff_after_flag_set gets called only after do_something finished on the background thread, or do_stuff_after_timeout gets called because the background thread took too long.


If you want to understand the problem with your (edited) existing code:

if not is_flag_set():
    with condition:
        condition.wait(120)

You're worried that the main thread can check the flag before the condition is set. Well, of course it can; you put the if before doing anything with the condition. That's why you have to use wait_for, or a while loop around wait, as in the example in the docs; it's the only way to be sure the flag is checked when you've been notified that it's ready to be checked.

Also, notice that you're not really synchronizing anything here. If the call to is_flag_set and set_flag don't happen inside the with condition:, the flag isn't synchronized between threads. (With CPython on most platforms, you will almost always get away with that, but if you're looking for what you can almost always get away with instead of what's correct, you really don't need a Condition in the first place…)


Some notes:

  • If you don't have Python 3.2 or later, you only have wait, not wait_for, and there's no way to tell whether the wait succeeded or timed out. Other than the timeout issue, c.wait_for(is_flag_set) is basically the same as while not is_flag_set(): c.wait(). So you can build wait_for yourself (notice that the docs link to the source), or you can find a backport on PyPI.

  • If you want to know why you need to do things this way, Wikipedia's Monitor article explains pretty nicely the problems that are solved by this extra complexity. (In the cases where you've thought through all the race conditions and know you don't need a Condition, use an Event instead.)

  • Notice that I used notify rather than notify_all (or notifyAll, which is another name for notify_all but has been deprecated since Python 2.6). If there's only one waiter, you only need to notify one waiter; it's simpler under the covers, and clearer about your intent. (If someone sees notify_all, they're liable to assume that you're using a thread pool, which you're not.)

  • Also notice that I put the do_stuff() inside the lock. This doesn't actually matter either way unless you're going to reset the flag and set it again later. But if it does, this protects you from "missed cycle" bugs.

  • Finally, it's not really true that you need a wait_for or a loop around wait; if the flag is only assigned once in the entire program, and the condition is only notified once, an if after the wait, instead of before as you tried, will have the same effect. (Then again, so will using an Event or other simpler sync object.) But it's better to just do it the safe way; then, you won't accidentally add races or deadlocks when you later edit something that doesn't seem related…

Sign up to request clarification or add additional context in comments.

2 Comments

I don't want to remove the timeout from the code. I want the main thread wait for at most 2 mins no matter what happens. I feel like, in this answer, the main thread wait until the background thread is finished.
Yes, in this answer, the main thread waits until the background thread is finished. But if you want it to wait no more than 2 minutes, just put the timeout back in (and decide what to do if it times out). If that isn't clear, I can edit the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.