Log in

View Full Version : Global lock for fftwf


vcmohan
16th December 2025, 12:47
I understand that all functions of fftwf dll excepting execute are not thread safe and so need to introduce global guard. avisynth+ has introduced an env-> function that facilitates this. When I google c++ about the Guard lock and mutex it mentioned that these are old and execution takes time. It recommends heap functions. I need help in coding as I do not have that expertise. Also vapourSynth may provide a mechanism for this purpose.

LoRd_MuldeR
16th December 2025, 23:49
When I google c++ about the Guard lock and mutex it mentioned that these are old and execution takes time. It recommends heap functions.

Not sure what you mean. Cleary, if each thread allocates its own separate buffer on the heap, so that no buffer actually needs to be shared between multiple threads, then no synchronization (e.g. mutex) will be needed.

But, for as long as you do have shared buffers, and if threads may need to modify the buffer contents, then proper synchronization is required!

The standard synchronization primitive to protect (serialize) access to a shared buffer is a mutex. In C++, this is implemented by the std::mutex class. Furthermore, the C++ (RAII) way to lock/unlock a mutex is std::lock_guard.


Now, the FFTW documentation is pretty clear, that all functions, except for fftw_execute(), are not thread-safe and therefore do need explicit synchronization, if you want to access them for different threads:
The upshot is that the only thread-safe routine in FFTW is fftw_execute (and the new-array variants thereof). All other routines (e.g. the planner) should only be called from one thread at a time. So, for example, you can wrap a semaphore lock around any calls to the planner; even more simply, you can just create all of your plans from one thread.

— https://www.fftw.org/fftw3_doc/Thread-safety.html


You can not change how FFTW works internally, or that most of its functions are not thread-safe. So, there is no way around proper synchronization, in a multi-threaded application.

The general pattern would be as follows:
std::mutex g_mutex; // <-- global mutex that must be used (locked) by any thread that wants to call some FFTW function, except for fftw_execute()

void my_thread_safe_wrapper_function()
{
// Locks the mutex here
std::lock_guard<std::mutex> guard(g_mutex);

// Now we can safely call the FFTW function that is *not* thread-safe
fftw_whatever();

// Mutex is automatically unlocked when `guard` goes out of scope
}


Of course, this will only work properly if every call to any of the "problematic" FFTW functions from any thread is protected (synchronized) by such a wrapper function.

vcmohan
17th December 2025, 13:18
Many thanks @LoRd_Mulder. I will try. In avisynth+ there is a globl lock code for version 12 and legacy mutex code for earlier versions. for ver 12 IScriptEnvironment pointer env provides a way for global lock. But earlier versions do not. I am therefore confused about the locking process. If you have time can you look into it?

Julek
19th December 2025, 14:25
Yu can:
1. use mutex inside getFrame.
2. allocate needed temp buff for each thread: https://github.com/HomeOfVapourSynthEvolution/VapourSynth-EEDI3/blob/master/EEDI3/EEDI3.cpp#L335-L393
3. use fmUnordered (vsapi->createVideoFilter): https://www.vapoursynth.com/doc/api/vapoursynth4.h.html#enum-vsfiltermode

LoRd_MuldeR
19th December 2025, 17:27
Looking at the example plugin code here:
https://github.com/vapoursynth/vapoursynth/blob/master/sdk/filter_skeleton.c

The function VapourSynthPluginInit2() will be called once per plug-in DLL, whereas the function filterCreate() will be called once for each filter instance that is created.

To my understanding, filterCreate() could be called multiple times in parallel, e.g., if multiple instances of the filter are created:
https://www.vapoursynth.com/doc/apireference.html#plugins

Consequently, even inside your filterCreate() function, you would have to synchronize every access to any FFTW function! (except for fftw_execute(), but this one probably is not used in filterCreate() anyway)

Meanwhile, I would assume that your filterGetFrame() function will only need to use one FFTW function, which is fftw_execute(). If that holds true, then no synchronization is needed there.

In other words, you create all your FFTW plans once in your filterCreate() function, which must be properly synchronized, but in your actual filterGetFrame() function you just use (via fftw_execute()) the plans that you have already created, so no further synchronization is needed. And, since filterCreate() only happens very rarely (once per instance), you probably do not need to worry about the "performance" of the synchronization primitive (e.g mutex) ;)

________

Now, which mutex should you use to synchronize on?

At the very least, you need to create a single "global" mutex in your plugin DLL, which will be used (locked) by all the filter instances that are created – probably needed only inside the filterCreate() function.

But, of course, this will not help, if some other plugin DLL may also call into the FFTW library :rolleyes:

Z2697
19th December 2025, 22:40
I think vcmohan is looking for a lock that works across multiple fftw depending plugins. The avs+ function he mentioned is exactly for that purpose.

StvG
20th December 2025, 06:51
Next time, provide more context so others can understand the problem better - https://github.com/AviSynth/AviSynthPlus/issues/444

vcmohan
20th December 2025, 12:56
Looking at the example plugin code here:
https://github.com/vapoursynth/vapoursynth/blob/master/sdk/filter_skeleton.c

you create all your FFTW plans once in your filterCreate() function, which must be properly synchronized, but in your actual filterGetFrame() function you just use (via fftw_execute()) the plans that you have already created, so no further synchronization is needed. And, since filterCreate() only happens very rarely (once per instance), you probably do not need to worry about the "performance" of the synchronization primitive (e.g mutex) ;)

________

Now, which mutex should you use to synchronize on?

...............

But, of course, this will not help, if some other plugin DLL may also call into the FFTW library :rolleyes:
Thanks. In my vcm plugin there are several function which use fftwf dll. Now in each of these functions I will use the mutex lock on fftwf_mallock ,fftwf_plan and other fftwf calls while in the init of creating the filter. In the frame I do not use any lock as I only use fftwf_execute calls.

But in the script one can include in the process two or more such functions.
Worried whether I will have any interference between functions. May be I should restrict fftwf to single thread only?

yuygfgg
20th December 2025, 16:03
Thanks. In my vcm plugin there are several function which use fftwf dll. Now in each of these functions I will use the mutex lock on fftwf_mallock ,fftwf_plan and other fftwf calls while in the init of creating the filter. In the frame I do not use any lock as I only use fftwf_execute calls.

But in the script one can include in the process two or more such functions.
Worried whether I will have any interference between functions. May be I should restrict fftwf to single thread only?

I think you will need a global mutex, and all functions should use that to lock their resources. For example, llvmexpr uses exactly the same technique to manage its jit cache. https://github.com/Sunflower-Dolls/Vapoursynth-llvmexpr/blob/b886e2e74efde6bd2697378e115034b83ef730b5/llvmexpr/runtime/llvm/Jit.hpp#L66

When all functions (and multiple instances in the script) are called, they are all accessing the same global mutex variable in memory. Therefore, even if different threads run them simultaneously, they will wait for each other.

vcmohan
21st December 2025, 12:49
Many thanks @YUYgfgg. In the script its possible that some user may call one function from my plugin that uses mutex for fftwf and another function from a different plugin also using fftwf . The mutexes can be different unless vapourSynth itself provides that global mutex and insists developers to use it for fftwf dll usage. Is my understanding correct?

LoRd_MuldeR
21st December 2025, 16:48
Many thanks @YUYgfgg. In the script its possible that some user may call one function from my plugin that uses mutex for fftwf and another function from a different plugin also using fftwf . The mutexes can be different unless vapourSynth itself provides that global mutex and insists developers to use it for fftwf dll usage. Is my understanding correct?

Yeah, if your plugin defines its own "global" mutex, then this can be used to synchronize all concurrent actions (e.g. calls to FFTW functions) within your plugin, but it will not synchronize with other plugins at all.

In other words, other plugins don't have a clue about "your" mutex, and they may call into FFTW independently whenever they want ;)

If you synchronize on a mutex that is provided by VapourSynth, then this may work to synchronize with other plugins - but only if all the other plugins happen to synchronize all of their relevant actions (e.g. calls to FFTW functions) on that very same mutex. I don't know if there is some sort of "best practice" that says that all VapourSynth plugins should be doing this. But, in general, you must not make any assumptions about what other plugins might do (or not).

Myrsloik
21st December 2025, 18:57
What you more or less have to do here is keep your own copy of the fftw library loaded so you can guard things with your own global mutex. Either by simply using static linking or if you use dynamic linking by renaming the dll to a distinct name no one else uses.

Not all solutions are elegant.

vcmohan
22nd December 2025, 12:45
Thanks for explaining and suggestions, if I keep FFTW dlls in my own folder and remove from windows system dll folders, then at runtime it will not work for others as I do dynamic linking. I will define my global mutex as static so that it will ensure that all functionn that plugin run without interfering with each other, In documenttation I will put a cautionary note. If vapourSynth provides a specific mutex for fftw dll then it solves most of the problems.

Myrsloik
22nd December 2025, 15:39
Thanks for explaining and suggestions, if I keep FFTW dlls in my own folder and remove from windows system dll folders, then at runtime it will not work for others as I do dynamic linking. I will define my global mutex as static so that it will ensure that all functionn that plugin run without interfering with each other, In documenttation I will put a cautionary note. If vapourSynth provides a specific mutex for fftw dll then it solves most of the problems.

The DLLs MUST HAVE A UNIQUE FILENAME. In windows if you load a DLL with the same filename a second time (path irrelevant) it will simply increment the reference count of the already loaded DLL and return a handle to the already loaded copy.

It's a fun quirk you rarely encounter but you have to know about to make this work.

Adub
22nd December 2025, 21:43
It would seem that recent versions of FFTW3 may have a solution to this problem: https://www.fftw.org/fftw3_doc/Thread-safety.html


Neither strategy works, however, in the following situation. The “application” is structured as a set of “plugins” which are unaware of each other, and for whatever reason the “plugins” cannot coordinate on grabbing the lock. (This is not a technical problem, but an organizational one. The “plugins” are written by independent agents, and from the perspective of each plugin’s author, each plugin is using FFTW correctly from a single thread.) To cope with this situation, starting from FFTW-3.3.5, FFTW supports an API to make the planner thread-safe:

void fftw_make_planner_thread_safe(void);

vcmohan
23rd December 2025, 12:59
Many thanks @Adub. I will check it. fftw still prefers buffer creation through its malloc. There are other functions of fftw which are also used. All these need to be thread safe. Request @LoRd_MuldeR and @Myrslok also to look into this.

Myrsloik
23rd December 2025, 21:16
Many thanks @Adub. I will check it. fftw still prefers buffer creation through its malloc. There are other functions of fftw which are also used. All these need to be thread safe. Request @LoRd_MuldeR and @Myrslok also to look into this.

Why do you need me to read the documentation? std::mutex is your friend.

The upshot is that the only thread-safe routine in FFTW is fftw_execute (and the new-array variants thereof). All other routines (e.g. the planner) should only be called from one thread at a time. So, for example, you can wrap a semaphore lock around any calls to the planner; even more simply, you can just create all of your plans from one thread.

You have std::aligned_alloc (https://en.cppreference.com/w/cpp/memory/c/aligned_alloc) for allocations in C++17 if you use a modern compiler which you should. Or just use a mutex for the fftw ones. It's not like it'll bottleneck anything anyway.