30

I have a pretty basic understanding of multithreading in Python and an even basic-er understanding of asyncio.

I'm currently writing a small Curses-based program (eventually going to be using a full GUI, but that's another story) that handles the UI and user IO in the main thread, and then has two other daemon threads (each with their own queue/worker-method-that-gets-things-from-a-queue):

  • a watcher thread that watches for time-based and conditional (e.g. posts to a message board, received messages, etc.) events to occur and then puts required tasks into...
  • the other (worker) daemon thread's queue which then completes them.

All three threads are continuously running concurrently, which leads me to some questions:

  • When the worker thread's queue (or, more generally, any thread's queue) is empty, should it be stopped until is has something to do again, or is it okay to leave continuously running? Do concurrent threads take up a lot of processing power when they aren't doing anything other than watching its queue?
  • Should the two threads' queues be combined? Since the watcher thread is continuously running a single method, I guess the worker thread would be able to just pull tasks from the single queue that the watcher thread puts in.
  • I don't think it'll matter since I'm not multiprocessing, but is this setup affected by Python's GIL (which I believe still exists in 3.4) in any way?
  • Should the watcher thread be running continuously like that? From what I understand, and please correct me if I'm wrong, asyncio is supposed to be used for event-based multithreading, which seems relevant to what I'm trying to do.
  • The main thread is basically always just waiting for the user to press a key to access a different part of the menu. This seems like a situation asyncio would be perfect for, but, again, I'm not sure.

Thanks!

5
  • How did you implement the queues? Do you use the Queue module and/or threading's synchronization primitives, or do you periodically check if there is data to be processed? Commented Jul 21, 2015 at 14:26
  • @Phillip Yes, I'm using threading locks -- wasn't aware that Queue had synchronization primitives. Is that a better way to do it? Commented Jul 21, 2015 at 16:57
  • @velocirabbit The Queue module provides thread-safe queues. You should definitely be using that over anything you created yourself. Commented Jul 21, 2015 at 18:35
  • @dano Oh, I'm definitely using Queue -- does it have built in synchronization? Is there no need to use both Queue and locks then? Commented Jul 21, 2015 at 18:38
  • @velocirabbit It depends on exactly how you're using the Queue, but if you're just using it to pass a message from a producer to a consumer, you should be able to use it without any external locking. You just need to make sure you stick to queue.get() on the consumer side, rather than some kind of loop that checks to see if the Queue is empty before doing the get. Commented Jul 21, 2015 at 18:40

1 Answer 1

22

When the worker thread's queue (or, more generally, any thread's queue) is empty, should it be stopped until is has something to do again, or is it okay to leave continuously running? Do concurrent threads take up a lot of processing power when they aren't doing anything other than watching its queue?

You should just use a blocking call to queue.get(). That will leave the thread blocked on I/O, which means the GIL will be released, and no processing power (or at least a very minimal amount) will be used. Don't use non-blocking gets in a while loop, since that's going to require a lot more CPU wakeups.

Should the two threads' queues be combined? Since the watcher thread is continuously running a single method, I guess the worker thread would be able to just pull tasks from the single queue that the watcher thread puts in.

If all the watcher is doing is pulling things off a queue and immediately putting it into another queue, where it gets consumed by a single worker, it sounds like its unnecessary overhead - you may as well just consume it directly in the worker. It's not exactly clear to me if that's the case, though - is the watcher consuming from a queue, or just putting items into one? If it is consuming from a queue, who is putting stuff into it?

I don't think it'll matter since I'm not multiprocessing, but is this setup affected by Python's GIL (which I believe still exists in 3.4) in any way?

Yes, this is affected by the GIL. Only one of your threads can run Python bytecode at a time, so won't get true parallelism, except when threads are running I/O (which releases the GIL). If your worker thread is doing CPU-bound activities, you should seriously consider running it in a separate process via multiprocessing, if possible.

Should the watcher thread be running continuously like that? From what I understand, and please correct me if I'm wrong, asyncio is supposed to be used for event-based multithreading, which seems relevant to what I'm trying to do.

It's hard to say, because I don't know exactly what "running continuously" means. What is it doing continuously? If it spends most of its time sleeping or blocking on a queue, it's fine - both of those things release the GIL. If it's constantly doing actual work, that will require the GIL, and therefore degrade the performance of the other threads in your app (assuming they're trying to do work at the same time). asyncio is designed for programs that are I/O-bound, and can therefore be run in a single thread, using asynchronous I/O. It sounds like your program may be a good fit for that depending on what your worker is doing.

The main thread is basically always just waiting for the user to press a key to access a different part of the menu. This seems like a situation asyncio would be perfect for, but, again, I'm not sure.

Any program where you're mostly waiting for I/O is potentially a good for for asyncio - but only if you can find a library that makes curses (or whatever other GUI library you eventually choose) play nicely with it. Most GUI frameworks come with their own event loop, which will conflict with asyncio's. You would need to use a library that can make the GUI's event loop play nicely with asyncio's event loop. You'd also need to make sure that you can find asyncio-compatible versions of any other synchronous-I/O based library your application uses (e.g. a database driver).

That said, you're not likely to see any kind of performance improvement by switching from your thread-based program to something asyncio-based. It'll likely perform about the same. Since you're only dealing with 3 threads, the overhead of context switching between them isn't very significant, so switching from that a single-threaded, asynchronous I/O approach isn't going to make a very big difference. asyncio will help you avoid thread synchronization complexity (if that's an issue with your app - it's not clear that it is), and at least theoretically, would scale better if your app potentially needed lots of threads, but it doesn't seem like that's the case. I think for you, it's basically down to which style you prefer to code in (assuming you can find all the asyncio-compatible libraries you need).

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

3 Comments

Thanks for the super detailed response! It's already helpful, but I'll try to clear up some details about what I'm trying to do: the watcher thread is just running through an infinite while loop checking to see if certain events occur. When one of those events occur, it puts a specific method into the worker's queue that the worker then carries out (the watcher has a queue that just has the function with the loop in it, but I just realized that's unnecessary as it doesn't consume anything else from either queue). Right now, the worker just continuously monitors for events.
So, thread summary: main thread handles curses UI and user I/O, watcher thread monitors various types of events (web posts, time-based, etc.), and worker carries out methods that watcher puts in its queue (can be things like make posts/responses, calculate things, etc.). If using asyncio wouldn't give me much of a performance increase, I might just use threading for now (since I'm on a deadline) and then rewrite it later with asyncio for practice.
upvoted, thank you for the super detailed response as well, my case is similar, i need to listen to a redis publisher in node.js to send data to python where i receive it, then need to create a numpy array to run some math on it like moving averages etc and then publish the results back to node.js with a separate publisher, has a few IO bound operations too like checking if the data sent by the publisher is continuous and if it detects gaps, poll an external API to get missing data, its a mixture of IO and CPU bound tasks, i am assuming asyncio only does IO well and would need processes ...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.