Messages vs Threads for concurrency - how robust is the Message loop for this purpose?


#1

Say you want to make an app that, details aside, basically does the following:

  • It continuously reads data from [something], could be Bluetooth or a serial port or a socket or anything, at a relatively constant rate.
  • It visually formats that data as it comes in and then displays it [somehow], updating (ideally) fast enough to pass the flicker fusion rate and appear continuous.

So as a concrete example, you might have an audio visualizer, reading in 88,200 bytes/sec and plotting something that makes you look like you're on acid.

This general use case leads pretty fast to some big-picture paradigmatic questions about how JUCE handles concurrent programming, and I'm interested in hearing what Jules has to say about the "best" ways to use JUCE for this sort of situation.

So one basic and general way to do it is to use Threads, something like this (without too many details):

  • A Thread for Reading - reads and buffers from the port at a constant rate
  • A Thread for Processing - slices and dices the data lots of times per second, doing so in its own thread to make sure it doesn't interfere with UI handling
  • A Thread for Plotting - this is actually just going to be the Message thread, I believe, since that's where JUCE does graphics stuff

Pretty basic. Then you have to deal with safely passing data between the three threads, signaling, etc. You might have a shared blob of memory with CriticalSections locking it, and then have threads use wait() and notify() and etc to signal one another and all that. This might be straightforward or an unbelievable pain in the ass, depending on the complexity of your application.

 

What I'm interested in is in seeing how well JUCE's other way of handling concurrency, the Message loop, enables us to get away from the above. The equivalent of the above would be the following:

  • A MessageListener for Reading - reads from the port and then sends a Message to the Processing actor when its internal buffer has attained sufficient critical mass to warrant it
  • A MessageListener for Processing - responds to Messages from Reading, processes the data in each Message, sends a new Message to Plotting 
  • A MessageListener for Plotting - plots

I'm envisioning MessageListeners as basically taking the roles of "actors" from the actor model. I really like the idea of it conceptually.

However, is this use case something the Message loop is built to handle?

It does mean the Message loop is going to get bombarded with lots of messages going back and forth between the actors. You can counteract this somewhat by controlling the granularity of messages being passed, like only passing messages every ~1/20th of a second or so with larger data lumps. But, in general, it's still a lot. Also, all of the DSP from that second actor is going to be done in the Message thread rather than its own thing, which might block stuff.

Would it slow down UI handling? What are best practices here? I'd love to just never use Threads again. However, I can't find much info on the intended uses for these things - can anyone help out?


#2

A reader thread + processor thread + GUI stuff as usual on the message thread is the way to do it.

The message thread can't be used for realtime things like reading data, because it can (and does) get blocked for unknown periods by event callbacks. And it can't be used for processing tasks because that'd block the GUI and make things unresponsive. These are exactly the types of task that threads were invented for!


#3

Curses! I guess my analysis was correct. Glad I asked before coding it up with messages...

I can think of a million nice ways to pass data betwen threads but I don't know which way works best with JUCE. The most natural way for me to think of it is something like this:

  • You have the three threads, as previously mentioned
  • When Reader accumulates enough data, it dumps its buffer and sends it in a message to Processor
  • Processor, which has a queue of messages, goes through each one serially and processes the data
  • When Processor finishes, it sends a message containing the processed data to the view Component in the GUI thread to plot it

However, I'm not sure this paradigm fits with the way JUCE does things. You can't send messages to threads, as far as I know. I can think of three ways to implement the above:

  1. Instead of passing messages, use a shared blob of data, CriticalSections, notify(), and various flags and logic
  2. Pass messages on the message thread to trigger things, but have the actual processing still happen in the separate threads
  3. Implement some kind of message queue for threads, so you can asynchronously send messages to each thread

What is the "right" way to do things from JUCE's perspective? #3 is really nice but seems like it involves a bit more work, and JUCE doesn't come with it under the hood. However, I thought that you might be able to easily implement #3 by creating a ThreadPool with just one Thread in it, so that you can asynchronously post ThreadPoolJobs to it, which can basically function like messages. Thoughts?


#4

option 3.

Create `Queue' class to which you can post messages. use a critialsection to lock the queue whenever post or pop. have a waitablevent that is signalled by the post method to wake the thread waiting on pop. conceptually use a "donation" model for your queue messages. thread A creates a message and posts to B, when posted A no longer "owns" the message, B does. The final consumer of the message deletes it.

at no time involve the UI message thread, except the final consumer of a message might want to async post an even to the UI (eg to update).

 

 


#5

Drop some. Give it an hour or so. Then when you're up launch the JUCE demo :)