Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting up idle thread/signalling thread

I'm using Python with wxPython for writing an app.

The method I'm considering to accomplish this may not be the best - if that's the case, let me know because I'm open to refactoring.

Right now, I have one GUI form. The main program start point instantiates an instance of the GUI form then runs wx.mainLoop(), which causes the app's main initial thread to block for the lifetime of the app.

We of course know that when events happen in the UI, the UI thread runs the code for them.

Now, I have another thread - a worker thread. This thread needs to sit idle, and then when something happens in the UI thread, e.g. a button is clicked, I want the worker thread to stop idling and do something else - run a function, say.

I can't envision this right now but I could see as the app gets more complex also having to signal the worker thread while it's actually busy doing something.

I have two questions about this setup:

  1. How can I make my worker thread idle without using up CPU time? Doing something like while True: pass will suck CPU time, while something like while True: time.sleep(0.1) will not allow instantaneous reaction to events.

  2. What's the best way to signal into the worker thread to do something? I don't want the UI thread to execute something, I want the worker thread to be signaled, by the UI thread, that it should change what it's doing. Ideally, I'd have some way for the worker thread to register a callback with the UI itself, so that when a button is clicked or any other UI Event happens, the worker thread is signalled to change what it's doing.

So, is this the best way to accomplish this? And what's the best way to do it?

Thanks!

like image 605
fdmillion Avatar asked May 09 '13 23:05

fdmillion


People also ask

What is the thread ID of the idle thread?

On a uniprocessor system, there is only one Idle thread and this idle thread will have the thread ID 0 (in process 0). On a multiprocessor system, however, Windows creates one Idle thread per CPU.

How do you make a thread wait for another thread in Python?

We can wait for a result using sleep. Specifically, the waiting thread can call the time. sleep() function and specify a number of seconds to wait. The thread will then block until the number of seconds has elapsed, before checking whether the new thread has completed and returned a result.

What happens if thread is not joined Python?

A Python thread is just a regular OS thread. If you don't join it, it still keeps running concurrently with the current thread. It will eventually die, when the target function completes or raises an exception.

How do you join a thread in Python?

A thread can be joined in Python by calling the Thread. join() method. This has the effect of blocking the current thread until the target thread that has been joined has terminated.


1 Answers

First: Do you actually need a background thread to sit around idle in the first place?

On most platforms, starting a new thread is cheap. (Except on Windows and Linux, where it's supercheap.) So, why not just kick off a thread whenever you need it? (It's just as easy to keep around a list of threads as a single thread, right?)

Alternatively, why not just create a ThreadPoolExecutor, and just submit jobs to it, and let the executor worry about when they get run and on which thread. Any time you can just think in terms of "tasks that need to get run without blocking the main thread" instead of "worker threads that need to wait on work", you're making your life easier. Under the covers, there's still one or more worker threads waiting on a queue, or something equivalent, but that part's all been written (and debugged and optimized) for you. All you have to write are the tasks, which are just regular functions.

But, if you want to write explicit background threads, you can, so I'll explain that.


How can I make my worker thread idle without using up CPU time? … What's the best way to signal into the worker thread to do something?

The way to idle a thread until a value is ready is to wait on a synchronization object. On any modern OS, waiting on a synchronization object means the operating system stops giving you any CPU time until the object is ready for you.*

There are a variety of different options you can see in the Threading module docs, but the obvious one to use in most cases like this is a Condition. The way to signal the worker thread is then to notify the Condition.

However, often a Queue is a lot simpler. To wait on a Queue, just call its get method with block=True. To signal another thread to wake up, just put something on the Queue. (Under the covers, a Queue wraps up a list or deque or other collection, a Lock, and a Condition, so you just tell it what you want to do—check for a value, block until there's a value, add a value—instead of dealing with waiting and signaling and protecting the collection.)

See the answer to controlling UI elements in wxPython using threading for how to signal in both directions, from a worker thread to a UI thread and vice-versa.


I'd have some way for the worker thread to register a callback with the UI itself, so that when a button is clicked or any other UI Event happens, the worker thread is signalled to change what it's doing.

You can do it this way if you want. Just pass self.queue.put or def callback(value): self.value = value; self.condition.notify() or whatever as a callback, and the GUI thread doesn't even have to know that the callback is triggering another thread.

In fact, that's a pretty nice design that may make you very happy later, when you decide to move some code back and forth between inline and background-threaded, or move it off to a child process instead of a background thread, or whatever.


I can't envision this right now but I could see as the app gets more complex also having to signal the worker thread while it's actually busy doing something.

But what do you want to happen if it's busy?

If you just want to say "If you're idle, wake up and do this task; otherwise, hold onto it and do it whenever you're ready", that's exactly what a Queue, or an Executor, will do for you automatically.

If you want to say, "If you're idle, wake up, otherwise, don't worry about it", that's what a Condition or Event will do.

If you want to say, "If you're idle, wake up and do this, otherwise, cancel what you're doing and do this instead", that's a bit more complicated. You pretty much need to have the background thread periodically check an "interrupt_me" variable while it's busy (and put a Lock around it), and then you'll set that flag as well as notifying the Condition… although in some cases, you can merge the idle and busy cases into a single Condition or Event (by calling an infinite wait() when idle, and a quick-check wait(timeout=0) when busy).


* In some cases—e.g., a Linux futex or a Windows CriticalSection—it may actually spin off a little bit of CPU time in some cases, because that happens to be a good optimization. But the point is, you're not asking for any CPU time until you're ready to use it.

like image 121
abarnert Avatar answered Sep 22 '22 18:09

abarnert