Do separated threads affect each other?


#1

Hello,
At first at all: does JUCE timerCallback provide separate thread?

That’s why I am asking:
I created own plugin which calculates FFT Radix-2. I coded it as that in processBlock I collect enough data to vector. When there is enough data collected, I block further collecting by bool flag like collectingData = false and set other bool flag - dataIsCollected. And in the mean time there is timer working and do nothing until dataIsCollected is true. If it’s true in timerCallback I calculate FFT. After FFT calculations I set collectingData = true to allow processBlock to collecting data again.

I separated it outside processBlock to be sure signal workflow in processBlock is not affected by FFT calculations for bigger buffer sizes like 32768. And it works, audio signal works without any lags.
But I found that twisting knobs or moving sliders in other plugins in the same process (for example Fabfilter Pro-Q2) are lagged. I wonder how to avoid that.

I found Voxengo SPAN analizer there is possible to set buffer size to 65536 (I am not sure but I suppose it’s buffer size for FFT), and it does not affect on performance in other plugins. Computer works as crazy, heating, and cooling fans roaring, but moving sliders or knobs work perfectly, with no lags. How to achieve that?

I thought my lags problem happens because I had to much points my Path which I draw on screen. So for debug I turned off drawing at all. And it didn’t help.

So I thought maybe my timer is too fast. So in timerCallback() I call stopTimer then make FFT calculations, and after that I startTimer again. To be sure, even if FFT is very slow (or very big buffer size) timerCallback will not overlap between each call. But it also didn’t help.

Any tips? Please?


#2

Timer callbacks are executed on the message thread, which also takes care of drawing your GUI (afaik) and is in general a low (or not so high) priority thread.

Your idea of moving the heavy calculations out of your audio thread is good! But as they are quite heavy they will block your message thread. Look at JUCE‘s Thread class, you can easily create your own thread with configurable priority and start it in your timer callback (or even in your processBlock once you have enough samples).


#3

Check out the MultithreadingDemo to see how to use JUCE Thread class:

Regarding the 65536 FFT size… I had similar frustrations when I started using FFTs… other plug-ins could use FFT sizes of 65536 and get a solid 60fps but my plug-in can only run at ~25fps using a size of 2048!

The problem is… an FFT size of 65536 requires 65536 samples to calculate, but if you’re using a sample rate of 48000Hz, it’s going to take ~1.4 seconds to fill the FFT buffer so your FPS is going to be less than 1…
I’ve found the best way to fix this problem is to recycle samples used in previous FFT buffers by having your GUI update at a consistent rate and for every frame ask the processor for the N most recent samples.


#4

Hello guys.
Thanks for your suggestions.
Could you also give me some tip what priority should I set in my purpose? I have no idea what is “priority”. I know it’s integer between 0 and 10. But what exactly it does? Or what priority other plugins in my DAW have? I don’t know any references. I read in documentation that audio thread has priority to 9. What has 10 priority?

Second question. I read manual but my English is poor and not sure if understand everything properly. I am not sure if I use Thread in proper manner.

I created my own class MyThread that inherits from Thread.

And in my pluginEditor class I created instance MyThread myThread;

And then in updateToggleState (click on toggle button) call myThread.startThread();

Is that all I need to run separate thread in my plugin? Is there any way to verify it’s really separate thread? Just for curious.

And to be strictly sure: Should I make all my heavy calculations in the Thread::run() ?
I found in tutorial and juce example they have some strange things in run(), like:

const MessageManagerLock mml (Thread::getCurrentThread());
if (! mml.lockWasGained())
    return;

They explain that but I don’t understand it. Is it necessary in my case also?

And all of that is in while(! threadShouldExit()) loop. With wait(someInterval). Is that equivalent of Timer::startTimer(someInterval). Maybe better would be if I also inherit from Timer and in run() just call startTimer(someInterval); like:

run()
{
      startTimer(someInterval);
}

What would be better?


#5

The priority of a thread dictates the order in which operations from different threads will run if the operations are cued to start at the same time. For example, consider the following two classes:

struct Alphabet  :   public Thread
{
    Alphabet() : Thread("Alphabet Thread") {}

    void run() override
    {
        for (int i = 0; i < 10; i++)
            std::cout << static_cast<char>(i + 65);
    }
};

struct Numbers   :   public Thread
{
    Numbers() : Thread("Numbers Thread") {}

    void run() override
    {
        for (int i = 0; i < 10; i++)
            std::cout << i;
    }
};

Now, if we call:

myAlphabetObject.startThread(0);
myNumbersObject.startThread(10);

The result is 0123456789ABCDEFGHIJ
However if we call:

myAlphabetObject.startThread(5);
myNumbersObject.startThread(5);

The result will be something like AB0C1D2E3F4G5H6I7J89 because both threads have equal priority and so both work ‘intertwined’ by doing almost one operation at a time.
If you don’t want your thread to be interrupting anything else, use a priority of 0, if you want it to run above everything else (and possibly interrupt your audio thread), use a priority of 10. Otherwise choose something in between (5 is probably a safe bet).

Yes, calling startThread() is the way to kick off your thread, you could do some tests if you liked to check it has in fact started a new thread (or use features of your IDE to check what threads are running).

AFAIK Timers always run on the message thread, so using run() override { startTimer(20) } will mean code in your timerCallback method is still running on the message thread.
Those ‘strange’ things in run() is a way of pausing the message thread while your own thread is running - this ensures you won’t be accidentally reading from or writing to the same variables on two threads at the same time.

You can mimic the behaviour of a Timer in your run() method like so:

void run() override
{
    // keep looping until stopThread() has been called somewhere
    while(!threadShouldExit())
    {
        // pause for some number of milliseconds.
        wait(intervalInMs);

        // try to lock the message thread...
        const MessageManagerLock mml(Thread::getCurrentThread());
        
        // exit run() if failed to lock
        if (!mml.lockWasGain())
            return;

        /* Any code called in this block now will run on this thread.
           Even any methods called from here will be executed on the thread. */
        doSomeCoolStuff();
}

#6

A common misconception about threads is, that it matters in which class you implement something, but that is completely irrelevant. Each thread has a call stack, that can call methods in any arbitrary class.

Like your program has a main() method, that is called from “outside”, the thread has a run() method, that is called from outside. When you have your debugger stopping at a breakpoint, you can see by the call stack, what methods are currently waiting for your code to return. And at the root of that call stack there is the thread and the name, that you gave in the thread constructor.

Like in all modern GUI frameworks, the main runs a message loop (MessageManager) to dispatch OS events to your program calling callback functions. Most of the times they are methods of Components, but also Timer callbacks and other thing.

In a plugin (AudioProcessor) it is a bit different, since it is only a set of methods offered to the host. And the convention is, that only processBlock() is called from the audio thread, everything else is called from the message thread (there are exceptions, that a host uses other threads to call things like setStateInformation, but that’s not the standard. Have a look through the forum using the search, if you run into these things).

Ideally your run() method is as independent as possible of other code. Often you have a FIFO buffer, where the processBlock can deliver audio data you want to have analysed. Make sure, there is a possibility to discard, if the data is not processed fast enough, otherwise you stall the audio.
The result should again be fetched from whatever needs the data, most of the time a Component’s paint() routine. Here you have to make sure to have the blocking time as short as possible.

So the example of @Im_Jimmi is a bit problematic, you don’t want to lock the message manager before you do something, but only for the moment, when the common data is accessed.

Here is an implementation for an analyser I wrote. You can take it as inspiration:


Since it only reads, after addAudioData did it’s job, it doesn’t need a lock here. But when the path is created in Analyser::createPath(), I need to get a lock in Line 98 to make sure, my analyser thread is not changing the values at the same time. The same lock is used in Line 79 to synchronise the independent analyser thread with the GUI (normal priority) thread. This technique would be a no-go for the audio thread, since it must not have to wait.

I hope that makes it a bit clearer.


#7

Hello guys.
I have sad news :slight_smile:
I’ve just moved all my drawing code and calculating FFT code to other thread, and it didn’t help. There are still lags (in Fabfilter Pro-Q2) on 32768 buffer size. What is more strange tried both startThread(0) and startThread(0), and see no difference.


#8

I can’t comment, why your code doesn’t run faster without looking into it.

But I am not surprised, that you don’t see a difference when you change the priority.
The OS uses the priority to decide, when a processor becomes available, what thread to run (scheduling). Only when the resources become scarce, it will matter what is of higher priority.


#9

Did you compare it in release mode? There should be a difference, if not there might be something wrong with your code. Maybe you could show it in a minimal version with some abbreviations what you do e.g. processFFT() instead of all your actual code


#10

Hey. In release it helped. Great. Now it works much better. But now I wonder if it was sense, that I moved all drawing and FFT code from timerCallback to another thread. In debug mode, there was no difference when I call it in timerCallback or in another thread.


#11

What exactly do you mean with drawing code? That one should remain in your components paint method. If you mean preprocessing of data in order to relieve your drawing thread that’s fine. And for that a separate thread is the best solution (as you did).

To elaborate a little: imagine your users’ machines aren’t as fast as your one. You want their UI be as reactive as on your machine, you can accomplish this by using Threads and giving them an adequate priority (not too high, so it won’t block your MessageThread, but also not too low in order to get the calculations going).


#12

Drawing code I mean all those Path::startNewSubPath(), Path::lineTo() (in the loop through all frequencies - of course not each freq bin is drawn, in the worst situation, there is 700 points), and Graphics::strokePath().


#13

Hey danielrudrich,
could you develope that mind:

…but also not too low in order to get the calculations going.

I thought in my case priority equal zero would be the best, until I don’t need any super Refresh rate or quality. What exactly do you mean “…to get the calculations going”

Do you think that with prioriy=0 on slower machines heavy calculations (like FFT buff size 65536) will not go at all? Or what?

Let’s back to the drawing code and threading.
To be more precise with what I mean “drawing code”:
I have class responsible for FFT calculations (let’s call it FFTclass) and in that class I have method like makeFFT() which perform FFT calculations on one buffer (for example 65536) and stores results in std::vector fftOutput (of course in that case fftOutput.size() is 65536).

Then I have other class (that inherits from component), let’s call it DrawClass. And in that class I have method like preparePath() where my vector fftOutput is used to create myPath with about 700 points. Then in the paint() method I call g.strokePath(myPath, PathStrokeType (0.5f);.

In myThread class I create instances of both classes:
FFTclass fftClass;
DrawClass drawClass;

In simplify that’s all. And that is how my run() method in myThread looks like:

run()
{
   fftClass.makeFFT();
   drawClass.preparePath();
   drawClass.repaint();
}

But what is more interesting for me: Let’s notice I have all that I need to show my graph on the screen but in fact it’s still not on the screen.
I MAKE IT VISIBLE ON THE SCREEN IN MY MAIN MESSAGE THREAD, by calling addAndMakeVisible(&myThread.drawClass); in the constructor, and setBounds() for it in resized() of my main thread.

Is that OK? I am not sure if it makes a sense if I make calculations and drawings in separate thread, but finally I make it visible in my main message thread like pluginEditor.


#14

Hello,
so could anyone help? Have you forgot about me? :slight_smile:
Does it make a sense to move all heave calculations and analyser graph drawing to separate Thread if you finally make it visible in AudioProcessorEditorby your main message thread by addAndMakeVisible(&threadComponent)?


#15

Did you read what I said about large FFT sizes taking ~1.4s to collect enough samples? May be where your performance issues are coming from… Also if your run() method literally is what you’ve shown:

… then this isn’t going to loop… run() is just called once when the thread starts (AFAIK?)… you need to implement the loop yourself as we showed previously.


#16

I don’t think it’s even allowed to call Component::repaint() from another thread, so I am not sure how the code is even running without crashing or similar problems? Perhaps the run() method isn’t even running in another thread at the moment? Was the thread started with startThread?


#17

Hey Jimmi,
sorry for that simplifying, of course I make while(! threadShouldExit()) loop. And inside of it I have also:

while(! threadShouldExit())
{
     wait (milisecToWait);
        
     const MessageManagerLock mml (Thread::getCurrentThread());
        
     if (! mml.lockWasGained())
            return;

     fftClass.makeFFT();
     drawClass.preparePath();
     drawClass.repaint();
}

And milisecToWait are dependent from buffer size. But it’s not the point, that’s why I simplified that. I’m just asking for idea in which location what should be called. And if it makes a sense what I’ve made in concern of that question?


#18

So what is the option to display on the screen component which is other thread than my main AudioProcessorEditor?

I have no idea where should I make it visible?


#19

I call repaint() from my run() method to repaint my spectrum component… haven’t had any issues. From what I understand repaint() just flags the component as “repaint this ASAP” and doesn’t actually call the paint() method directly.


#20

Yep, it is fine, if you added a MM-lock, otherwise this should assert:

Also correct, but the area that is marked dirty is a non atomic member, so it has to be guarded against non atomic access…