About the Fl::lock() call to initialize threading support

72 views
Skip to first unread message

Manolo

unread,
Feb 26, 2026, 2:59:28 AMFeb 26
to fltk.coredev
Among many other changes, commit eadea6a (dated 19 jun 2025)
added a detail to the documentation of Fl::lock() in FLTK 1.5 
that the Fl::lock() call to initialize threading support should be done
"before any windows are shown".

The test program that showcases FLTK threading support, threads,
calls Fl::lock() after its two windows have been show()'n.
This program runs OK.

Why was "before any windows are shown" added to the documentation?


imacarthur

unread,
Mar 7, 2026, 5:29:03 AMMar 7
to fltk.coredev
Manolo,

Did you get anywhere with this?
I don't know why the docs are this way - but then it is also stating a thing that I have long believed is true.
So... I believe it is true, but I don't have any actual evidence that is actually is.
There might be weird effects with event delivery if the events queue is not initially locked, when subsidiary threads are running, but how closely that relates to the first window being shown may depend very much on the application in question, I think.

So I have nothing useful to offer here.
(That said, I'm going to go on doing that first lock really early in my code anyway!)

Cheers,

Ian

Bill Spitzak

unread,
Mar 9, 2026, 1:20:06 PMMar 9
to fltkc...@googlegroups.com
I think the real rule is that you must call Fl::lock() in the main thread before any other thread calls it. You can run fltk single-threaded for as long as you want before this.


--
You received this message because you are subscribed to the Google Groups "fltk.coredev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkcoredev...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/fltkcoredev/fc5cbd27-3152-4521-ad6d-40c03cf83b10n%40googlegroups.com.

Manolo

unread,
Mar 11, 2026, 3:17:36 AMMar 11
to fltk.coredev
Le lundi 9 mars 2026 à 18:20:06 UTC+1, spi...@gmail.com a écrit :
I think the real rule is that you must call Fl::lock() in the main thread before any other thread calls it. You can run fltk single-threaded for as long as you want before this.

Thanks Bill.
That's what I think too. Therefore I'd like Matthias to detail why he added the condition "call Fl::lock() before 
the first window show() operation" to the documentation.

melcher....@googlemail.com

unread,
Mar 13, 2026, 5:54:41 AMMar 13
to fltk.coredev

RFC at the bottom of this mail!

Sorry for my late reply. I added this condition because Fl_Window::show() can already trigger drawing calls which should be correctly locked if locking is needed. A better wording would probably be that Fl::lock() must be called from the main thread exactly once before any thread is launched that wants to use locking. Suggested new comment for Fl::lock():

/**
\brief Initialize threading support OR acquire the global UI lock for FLTK.

\warning Fl::lock() has TWO DISTINCT uses - do not confuse them or deadlocks will occur!

\b INITIALIZATION \b (main \b thread \b only):
The main thread must call Fl::lock() exactly ONCE before any thread is launched
that may want to call Fl::lock/unlock. This initialization call will return
non-zero if threading is not available on the platform.

\b LOCKING \b (worker \b threads \b only):
Worker threads must call Fl::lock() before accessing any FLTK widgets or data.
The lock() method blocks the worker thread until it can safely access FLTK.
Worker threads must pair each Fl::lock() call with Fl::unlock() and should
call Fl::awake() after unlocking to notify the main thread of UI updates.

\b CRITICAL \b RULE:
- Main thread: Call `Fl::lock()` ONCE for initialization, then NEVER call it again
- Worker threads: Call `Fl::lock()` every time before accessing FLTK, then `Fl::unlock()`

Worker threads must not create, show, or hide windows.

\return 0 if threading is available on the platform; non-zero
otherwise (only meaningful for the initial initialization call).

\see \ref advanced_multithreading
\see Fl::unlock()
\see Fl::awake()
*/

Similarly, in advanced.dox,

The main() thread must call Fl::lock() once before any windows are shown, to enable the internal lock (it is "off" by default since it is not useful in single-threaded applications) but thereafter the main() thread lock is managed by the library internally.

becomes

The main() thread must call Fl::lock() once before any any thread is launched that may want to call Fl::lock/unlock, to enable the internal lock (it is "off" by default since it is not useful in single-threaded applications) but thereafter the main() thread lock is managed by the library internally.


RFC 1: I suggest that starting with FLTK 1.5, we add Fl::init_locking() which ensures that it is called from within the main thread. It would resolve the dual use of Fl::lock() (which could still keep its original functionality for back compatibility). We could then also have Fl::deinit_locking(), but I doubt that that is ever really used.

RFC 2: in the rare case where a user wants to lock FLTK in the main thread, but outside of Fl::wait() or Fl::run(), we could document fl_lock_function() and fl_unlock_function(). Do we want to make that public? 


melcher....@googlemail.com

unread,
Mar 13, 2026, 6:09:19 AMMar 13
to fltk.coredev
I also suggest to update the comment for Fl::awake() to include the notion that Fl::lock() must have been called for Fl::awake() to work from within a thread:

/**
\brief Notifies the main GUI thread from a worker thread.

In FLTK, worker threads can update the UI, but all UI changes must be wrapped
between Fl::lock() and Fl::unlock(). After calling Fl::unlock(), the worker
thread should call Fl::awake() to signal the main thread that
updates are pending.

It's \e not necessary to wrap calls to any form of Fl::awake() by Fl::lock()
and Fl::unlock(). Nevertheless, the early, single call to Fl::lock() used to
initialize threading support is necessary.

Fl::awake() is typically called by worker threads, but it can be used safely
by the main thread too, as a means to break the event loop.

\note Worker threads must not create, show, or hide windows.


Manolo

unread,
Mar 13, 2026, 12:56:27 PMMar 13
to fltk.coredev
Many thanks Matthias for your detailed reply.

I fully agree with this proposal.
 

Similarly, in advanced.dox,

The main() thread must call Fl::lock() once before any windows are shown, to enable the internal lock (it is "off" by default since it is not useful in single-threaded applications) but thereafter the main() thread lock is managed by the library internally.

becomes

The main() thread must call Fl::lock() once before any  thread is launched that may want to call Fl::lock/unlock, to enable the internal lock (it is "off" by default since it is not useful in single-threaded applications) but thereafter the main() thread lock is managed by the library internally.
I would prefer this simpler version:
The main() thread must call Fl::lock() once before any  thread is launched that may want to call Fl::lock/unlock, to enable the internal lock (it is "off" by default).



RFC 1: I suggest that starting with FLTK 1.5, we add Fl::init_locking() which ensures that it is called from within the main thread. It would resolve the dual use of Fl::lock() (which could still keep its original functionality for back compatibility).
I agree.
 
We could then also have Fl::deinit_locking(), but I doubt that that is ever really used.
I doubt too.
 

RFC 2: in the rare case where a user wants to lock FLTK in the main thread, but outside of Fl::wait() or Fl::run(), we could document fl_lock_function() and fl_unlock_function(). Do we want to make that public? 
I don't support this proposal that would be confusing.
 

Manolo

unread,
Mar 13, 2026, 1:03:28 PMMar 13
to fltk.coredev
Le vendredi 13 mars 2026 à 11:09:19 UTC+1, melcher....@googlemail.com a écrit :
I also suggest to update the comment for Fl::awake() to include the notion that Fl::lock() must have been called for Fl::awake() to work from within a thread:

/**
\brief Notifies the main GUI thread from a worker thread.

In FLTK, worker threads can update the UI, but all UI changes must be wrapped
between Fl::lock() and Fl::unlock(). After calling Fl::unlock(), the worker
thread should call Fl::awake() to signal the main thread that
updates are pending.

It's \e not necessary to wrap calls to any form of Fl::awake() by Fl::lock()
and Fl::unlock().
OK
 
Nevertheless, the early, single call to Fl::lock() used to
initialize threading support is necessary.
This sentence seems confusing to me because it mentions something to do once in 
the main thread while discussing things to do in worker threads. I suggest to either
remove it or place it at the end of the Fl::awake() description and with explicit
mention it's for the main thread:
The early, single call to Fl::lock() by the main thread used to
initialize threading support is necessary before worker threads begin using Fl::awake().


Fl::awake() is typically called by worker threads, but it can be used safely
by the main thread too, as a means to break the event loop.

\note Worker threads must not create, show, or hide windows.
OK
 

Albrecht Schlosser

unread,
Mar 14, 2026, 12:58:36 PMMar 14
to fltkc...@googlegroups.com
On 3/13/26 10:54 Matthias Melcher wrote:

RFC ...

Good so far (i.e. I would agree with this wording), but if we added Fl::init_locking() - which I also support - then the entire documentation would have to be rewritten again, also mentioning that Fl::lock() called by users in the main thread is now deprecated. Or something like this.

Shouldn't we first discuss (vote for) the following additional RFC(s)?

RFC 1: I suggest that starting with FLTK 1.5, we add Fl::init_locking() which ensures that it is called from within the main thread. It would resolve the dual use of Fl::lock() (which could still keep its original functionality for back compatibility).

OK (+1), but (sorry for my repeated "but") I don't know what exactly you mean with "which ensures that it is called from within the main thread". How would it *ensure* that? Or do you simply mean that it would be clearer that Fl::init_locking() would only be called once in the main thread (under the programmer's responsibility) whereas Fl::lock() would be called anywhere else, i.e. *only* in threads by user code and in the main thread by FLTK itself?


We could then also have Fl::deinit_locking(), but I doubt that that is ever really used.

-1, because I don't think that it would be useful. We could consider to add this if users request it and provide a plausible explanation for why they need it.


RFC 2: in the rare case where a user wants to lock FLTK in the main thread, but outside of Fl::wait() or Fl::run(), we could document fl_lock_function() and fl_unlock_function(). Do we want to make that public?

-1, this would be (a) confusing and useless and (b) would unnecessarily publish implementation details.


Albrecht Schlosser

unread,
Mar 14, 2026, 1:37:04 PMMar 14
to fltkc...@googlegroups.com


On 3/13/26 11:09 Matthias Melcher wrote:
I also suggest to update the comment for Fl::awake() to include the notion that Fl::lock() must have been called for Fl::awake() to work from within a thread:

/**
\brief Notifies the main GUI thread from a worker thread.
In FLTK, worker threads can update the UI, but all UI changes must be wrapped
between Fl::lock() and Fl::unlock(). After calling Fl::unlock(), the worker
thread should call Fl::awake() to signal the main thread that
updates are pending.

We could maybe add here that updating the UI can also be done by calling Fl::awake(awake_handler, ...) which would then be executed in the context of the main thread without additional locking by the worker thread.


It's \e not necessary to wrap calls to any form of Fl::awake() by Fl::lock()
and Fl::unlock(). Nevertheless, the early, single call to Fl::lock() used to
initialize threading support is necessary.

... or Fl::init_locking() if we added this.


Fl::awake() is typically called by worker threads, but it can be used safely
by the main thread too, as a means to break the event loop.
\note Worker threads must not create, show, or hide windows.

OK, +1 in most parts, see comments above and below.

Well, I'm not sure if the 2nd part of "it can be used safely by the main thread too, as a means to break the event loop" is still applicable or at all useful. What would "break the event loop" mean in practice? I believe that the first part "it can be used safely by the main thread" is true because it doesn't have any noticeable effect at all.

Rationale:

1. Since FLTK 1.4 all user code except the code in the main thread prior to calling Fl::run() [or, to be precise: any loop that uses Fl::wait()] is executed in the context of the main thread, i.e. it's called by the core FLTK loop that services events, callbacks, timeouts, etc..

2. Prior to FLTK 1.4 Windows and macOS used system timers which could be executed asynchronously to the event loop. In such timer callbacks it could have been useful to "break the event loop" by calling Fl::awake(), although I can't imagine what exactly could be achieved by this. But I remember that there was (and still is [1]) something in the macOS specific code to "break the event loop".

3. On all platforms Fl::awake() sends some kind of "message" to the FLTK core that is read and interpreted inside FLTK's event processing loop, specific to the platform. Therefore, user code that calls Fl::awake() in the main thread to "break the event processing loop" will likely not cause any noticeable effect. Unlike I'm missing something: please enlighten me if this is the case.

------------------------

[1] See this code:
src/Fl_cocoa.mm-526-void Fl_Cocoa_Screen_Driver::breakMacEventLoop()
src/Fl_cocoa.mm-527-{
src/Fl_cocoa.mm-528-  NSEvent *event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
src/Fl_cocoa.mm-529-                                      location:NSMakePoint(0,0)
src/Fl_cocoa.mm-530-                                 modifierFlags:0 timestamp:0
src/Fl_cocoa.mm-531-                                  windowNumber:0 context:NULL
src/Fl_cocoa.mm-532-                                       subtype:FLTKBreakLoopEvent
src/Fl_cocoa.mm-533-                                         data1:0
src/Fl_cocoa.mm-534-                                         data2:0];
src/Fl_cocoa.mm-535-  [NSApp postEvent:event atStart:NO];
src/Fl_cocoa.mm-536-}

... which is indeed called inside macOS specific DND handling and maybe more, but very likely not by user code because it's not a public (documented) function.

Albrecht Schlosser

unread,
Mar 14, 2026, 1:42:54 PMMar 14
to fltkc...@googlegroups.com
On 3/13/26 18:03 Manolo wrote:
Le vendredi 13 mars 2026 à 11:09:19 UTC+1, melcher....@googlemail.com a écrit :
 
Nevertheless, the early, single call to Fl::lock() used to
initialize threading support is necessary.

This sentence seems confusing to me because it mentions something to do once in 
the main thread while discussing things to do in worker threads. I suggest to either
remove it or place it at the end of the Fl::awake() description and with explicit
mention it's for the main thread:
The early, single call to Fl::lock() by the main thread used to
initialize threading support is necessary before worker threads begin using Fl::awake().

I don't think that this sentence is too confusing for users, it's more like additional information (which I think is good in this context).

However, I wouldn't mind changing it to something similar to what Manolo suggested.
Reply all
Reply to author
Forward
0 new messages