summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FL/Fl.H33
-rw-r--r--documentation/src/advanced.dox62
-rw-r--r--src/Fl_System_Driver.H14
-rw-r--r--src/Fl_lock.cxx302
-rw-r--r--src/Fl_win32.cxx2
-rw-r--r--src/drivers/Posix/Fl_Posix_System_Driver.cxx2
-rw-r--r--test/threads.cxx6
7 files changed, 247 insertions, 174 deletions
diff --git a/FL/Fl.H b/FL/Fl.H
index 245d9a562..d2aeeb1c7 100644
--- a/FL/Fl.H
+++ b/FL/Fl.H
@@ -346,12 +346,6 @@ public:
static bool idle() { return (idle_ != nullptr); }
#ifndef FL_DOXYGEN
-private:
- static Fl_Awake_Handler *awake_ring_;
- static void **awake_data_;
- static int awake_ring_size_;
- static int awake_ring_head_;
- static int awake_ring_tail_;
public:
static const char* scheme_;
static Fl_Image* scheme_bg_;
@@ -361,10 +355,6 @@ public:
static int menu_linespacing_; // STR #2927
#endif
-
- static int add_awake_handler_(Fl_Awake_Handler, void*);
- static int get_awake_handler_(Fl_Awake_Handler&, void*&);
-
public:
// API version number
@@ -1366,23 +1356,22 @@ public:
cut/paste shortcut.
*/
static int dnd_text_ops() { return option(OPTION_DND_TEXT); }
+
+ // --- FLTK Multithreading support functions ---
/** \defgroup fl_multithread Multithreading support functions
fl multithreading support functions declared in <FL/Fl.H>
@{ */
-
- // Multithreading support:
+ // Thread locking:
static int lock();
static void unlock();
- static void awake(void* message = 0);
- /** See void awake(void* message=0). */
- static int awake(Fl_Awake_Handler cb, void* message = 0);
- /**
- The thread_message() method returns the last message
- that was sent from a child by the awake() method.
-
- See also: \ref advanced_multithreading
- */
- static void* thread_message(); // platform dependent
+ // Thread wakup and defered calls:
+ static void awake();
+ FL_DEPRECATED("since 1.5.0 - use Fl::awake() or Fl::awake(handler, user_data) instead",
+ static void awake(void* message));
+ static int awake(Fl_Awake_Handler handler, void* user_data=nullptr);
+ static int awake_once(Fl_Awake_Handler handler, void* user_data=nullptr);
+ FL_DEPRECATED("since 1.5.0 - use Fl::awake() or Fl::awake(handler, user_data) instead",
+ static void* thread_message()); // platform dependent
/** @} */
/** \defgroup fl_del_widget Safe widget deletion support functions
diff --git a/documentation/src/advanced.dox b/documentation/src/advanced.dox
index 19ee40041..ff595139c 100644
--- a/documentation/src/advanced.dox
+++ b/documentation/src/advanced.dox
@@ -161,50 +161,6 @@ with calls to Fl::lock() and Fl::unlock():
To trigger a refresh of the GUI from a worker thread, the
worker code should call Fl::awake()
-<H3>Using Fl::awake thread messages</H3>
-You can send messages from worker threads to the \p main() thread
-using Fl::awake(void* message).
-If using this thread message interface, your \p main() might
-look like this:
-
-\code
- int main(int argc, char **argv) {
- /* Create your windows and widgets here */
-
- Fl::lock(); /* "start" the FLTK lock mechanism */
-
- /* show your window */
- main_win->show(argc, argv);
-
- /* start your worker threads */
- ... start threads ...
-
- /* Run the FLTK loop and process thread messages */
- while (Fl::wait() > 0) {
- if ((next_message = Fl::thread_message()) != NULL) {
- /* process your data, update widgets, etc. */
- ...
- }
- }
-
- /* terminate any pending worker threads */
- ... stop threads ...
-
- return 0;
- }
-\endcode
-
-Your worker threads can send messages to the \p main() thread
-using Fl::awake(void* message):
-
-\code
- void *msg; // "msg" is a pointer to your message
- Fl::awake(msg); // send "msg" to main thread
-\endcode
-
-A message can be anything you like. The \p main() thread can retrieve
-the message by calling Fl::thread_message().
-
<H3>Using Fl::awake callback messages</H3>
You can also request that the \p main() thread call a function on behalf of
the worker thread by using Fl::awake(Fl_Awake_Handler cb, void* userdata).
@@ -245,19 +201,11 @@ consumed the data, thereby allowing the
worker thread to re-use or update \p userdata.
\warning
-The mechanisms used to deliver Fl::awake(void* message)
-and Fl::awake(Fl_Awake_Handler cb, void* userdata) events to the
-\p main() thread can interact in unexpected ways on some platforms.
-Therefore, for reliable operation, it is advised that a program use
-either Fl::awake(Fl_Awake_Handler cb, void* userdata) or
-Fl::awake(void* message), but that they never be intermixed. Calling
-Fl::awake() with no parameters should be safe in either case.
-\par
-If you have to choose between using the Fl::awake(void* message)
-and Fl::awake(Fl_Awake_Handler cb, void* userdata) mechanisms and
-don't know which to choose, then try the
-Fl::awake(Fl_Awake_Handler cb, void* userdata) method first as it
-tends to be more powerful in general.
+The Fl::awake(void* message) call has been deprecated because the API was not
+sufficient to ensure the deliver of all message or the order of messages. The
+cal still exists for back compatibility, but should be repleaced with a call
+to Fl::awake(), Fl::awake(handler, user_data),
+or Fl::awake_once(handler, user_data).
\section advanced_multithreading_lockless FLTK multithreaded "lockless programming"
diff --git a/src/Fl_System_Driver.H b/src/Fl_System_Driver.H
index e306e7e74..2ec99745a 100644
--- a/src/Fl_System_Driver.H
+++ b/src/Fl_System_Driver.H
@@ -63,8 +63,20 @@ protected:
// implement once for each platform
static Fl_System_Driver *newSystemDriver();
Fl_System_Driver();
- static bool awake_ring_empty();
int filename_relative_(char *to, int tolen, const char *from, const char *base, bool case_sensitive);
+
+ // -- Awake handler stuff --
+public:
+ static Fl_Awake_Handler *awake_ring_;
+ static void **awake_data_;
+ static int awake_ring_size_;
+ static int awake_ring_head_;
+ static int awake_ring_tail_;
+ static bool awake_pending_;
+ static int push_awake_handler(Fl_Awake_Handler, void*, bool once);
+ static int pop_awake_handler(Fl_Awake_Handler&, void*&);
+ static bool awake_ring_empty();
+
public:
virtual ~Fl_System_Driver();
static int command_key;
diff --git a/src/Fl_lock.cxx b/src/Fl_lock.cxx
index 117d047a5..41c050e94 100644
--- a/src/Fl_lock.cxx
+++ b/src/Fl_lock.cxx
@@ -1,7 +1,7 @@
//
// Multi-threading support code for the Fast Light Tool Kit (FLTK).
//
-// Copyright 1998-2021 by Bill Spitzak and others.
+// Copyright 1998-2025 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@@ -34,7 +34,6 @@
Starting with 1.1.8, we now have a callback so that you can
process awake() messages as they come in.
-
The API:
Fl::lock() - recursive lock. You must call this before the
@@ -54,23 +53,47 @@
Fl::awake() call, or returns NULL if none. WARNING: the
current implementation only has a one-entry queue and only
returns the most recent value!
+
+ From Matt:
+
+ 25 years later, we have blazing fast CPUs with 24 cores and more.
+ Fl::awake(void*) is no longer useful as "the last value" could have
+ been overwritten by another thread before the main thread gets to it.
+ Also, the ring buffer may potentially fill up much faster than expected,
+ so I introduced Fl::awake_once(void (*cb)(void *), void*) which removes
+ duplicate entries in the queue.
*/
#ifndef FL_DOXYGEN
-Fl_Awake_Handler *Fl::awake_ring_;
-void **Fl::awake_data_;
-int Fl::awake_ring_size_;
-int Fl::awake_ring_head_;
-int Fl::awake_ring_tail_;
+
+static constexpr int AWAKE_RING_SIZE = 1024;
+Fl_Awake_Handler *Fl_System_Driver::awake_ring_ = nullptr;
+void **Fl_System_Driver::awake_data_ = nullptr;
+int Fl_System_Driver::awake_ring_size_ = 0;
+int Fl_System_Driver::awake_ring_head_ = 0;
+int Fl_System_Driver::awake_ring_tail_ = 0;
+bool Fl_System_Driverawake_pending_ = false;
+
#endif
-static const int AWAKE_RING_SIZE = 1024;
-/** Adds an awake handler for use in awake(). */
-int Fl::add_awake_handler_(Fl_Awake_Handler func, void *data)
+/**
+ \brief Adds an awake handler for use in awake().
+
+ \internal Adds an awake handler for use in awake().
+
+ \param[in] func The function to call when the main thread is awake.
+ \param[in] data The user data to pass to the function.
+ \param[in] once If true, the handler will be added only once, removing any
+ existing handler with the same function pointer and data pointer.
+ \return 0 on success, -1 if the ring buffer is full.
+ */
+int Fl_System_Driver::push_awake_handler(Fl_Awake_Handler func, void *data, bool once)
{
int ret = 0;
Fl::system_driver()->lock_ring();
+
+ // Allocate the ring buffers if we have not done so yet.
if (!awake_ring_) {
awake_ring_size_ = AWAKE_RING_SIZE;
awake_ring_ = (Fl_Awake_Handler*)malloc(awake_ring_size_*sizeof(Fl_Awake_Handler));
@@ -78,6 +101,28 @@ int Fl::add_awake_handler_(Fl_Awake_Handler func, void *data)
// explicitly initialize the head and tail indices
awake_ring_head_= awake_ring_tail_ = 0;
}
+
+ // If we want to add the handler only once, go through the list of existing
+ // handlers and remove any handler with the same function pointer
+ // and data pointer.
+ if (once) {
+ int src = awake_ring_tail_;
+ int dst = awake_ring_tail_;
+ while (src != awake_ring_head_) {
+ if ((awake_ring_[src] != func) || (awake_data_[src] != data)) {
+ if (src != dst) {
+ awake_ring_[dst] = awake_ring_[src];
+ awake_data_[dst] = awake_data_[src];
+ }
+ dst++;
+ if (dst >= awake_ring_size_) dst = 0; // wrap around
+ }
+ src++;
+ if (src >= awake_ring_size_) src = 0; // wrap around
+ }
+ awake_ring_head_ = dst;
+ }
+
// The next head index we will want (not the current index):
// We use this to check if the ring-buffer is full or not
// (and to update awake_ring_head_ if we do use the current index.)
@@ -85,21 +130,25 @@ int Fl::add_awake_handler_(Fl_Awake_Handler func, void *data)
if (next_head >= awake_ring_size_) {
next_head = 0;
}
- // check that the ring buffer is not full, and that it exists
- if ((!awake_ring_) || (next_head == awake_ring_tail_)) {
- // ring is non-existent or full. Return -1 as an error indicator.
+ // check that the ring buffer is not full
+ if (next_head == awake_ring_tail_) {
+ // ring is full. Return -1 as an error indicator.
ret = -1;
} else {
awake_ring_[awake_ring_head_] = func;
awake_data_[awake_ring_head_] = data;
awake_ring_head_ = next_head;
}
+
Fl::system_driver()->unlock_ring();
return ret;
}
-/** Gets the last stored awake handler for use in awake(). */
-int Fl::get_awake_handler_(Fl_Awake_Handler &func, void *&data)
+/**
+ \brief Gets the last stored awake handler for use in awake().
+ \internal Used in the main event loop when an Awake message is received.
+ */
+int Fl_System_Driver::pop_awake_handler(Fl_Awake_Handler &func, void *&data)
{
int ret = 0;
Fl::system_driver()->lock_ring();
@@ -118,100 +167,177 @@ int Fl::get_awake_handler_(Fl_Awake_Handler &func, void *&data)
}
/**
- Let the main thread know an update is pending and have it call a specific function.
- Registers a function that will be
- called by the main thread during the next message handling cycle.
- Returns 0 if the callback function was registered,
- and -1 if registration failed. Over a thousand awake callbacks can be
- registered simultaneously.
-
- \see Fl::awake(void* message=0)
-*/
-int Fl::awake(Fl_Awake_Handler func, void *data) {
- int ret = add_awake_handler_(func, data);
- Fl::awake();
- return ret;
+ \brief Checks if the awake ring buffer is empty.
+ \internal Used in the main event loop when an Awake message is received.
+ */
+bool Fl_System_Driver::awake_ring_empty() {
+ Fl::system_driver()->lock_ring();
+ bool retval = (awake_ring_head_ == awake_ring_tail_);
+ Fl::system_driver()->unlock_ring();
+ return retval;
}
-/** \fn int Fl::lock()
- The lock() method blocks the current thread until it
- can safely access FLTK widgets and data. Child threads should
- call this method prior to updating any widgets or accessing
- data. The main thread must call lock() to initialize
- the threading support in FLTK. lock() will return non-zero
- if threading is not available on the platform.
+/**
+ \brief Notifies the main GUI thread from a worker thread.
- Child threads must call unlock() when they are done
- accessing FLTK.
+ 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.
- When the wait() method is waiting
- for input or timeouts, child threads are given access to FLTK.
- Similarly, when the main thread needs to do processing, it will
- wait until all child threads have called unlock() before processing
- additional data.
+ \note Worker threads must not create, show, or hide windows.
- \return 0 if threading is available on the platform; non-zero
- otherwise.
+ \see \ref advanced_multithreading
+ \see Fl::awake(Fl_Awake_Handler, void*)
+ \see Fl::awake_once(Fl_Awake_Handler, void*)
+ */
+void Fl::awake() {
+ Fl::system_driver()->awake(nullptr);
+}
- See also: \ref advanced_multithreading
-*/
-/** \fn void Fl::unlock()
- The unlock() method releases the lock that was set
- using the lock() method. Child
- threads should call this method as soon as they are finished
- accessing FLTK.
+/**
+ \brief Awake the main GUI thread and leave a message pointer.
- See also: \ref advanced_multithreading
-*/
-/** \fn void Fl::awake(void* msg)
- Sends a message pointer to the main thread,
- causing any pending Fl::wait() call to
- terminate so that the main thread can retrieve the message and any pending
- redraws can be processed.
-
- Multiple calls to Fl::awake() will queue multiple pointers
- for the main thread to process, up to a system-defined (typically several
- thousand) depth. The default message handler saves the last message which
- can be accessed using the
- Fl::thread_message() function.
-
- In the context of a threaded application, a call to Fl::awake() with no
- argument will trigger event loop handling in the main thread. Since
- it is not possible to call Fl::flush() from a subsidiary thread,
- Fl::awake() is the best (and only, really) substitute.
-
- 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.
-
- Function Fl::awake() in all its forms 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.
-
- \see \ref advanced_multithreading
-*/
+ \deprecated Use Fl::awake() or Fl::awake(Fl_Awake_Handler, void*) instead.
+
+ This method is deprecated. The API can not ensure that Fl::thread_message()
+ returns the messages sent by Fl::awake(void *v) complete and in the correct
+ order.
+ Use Fl::awake() instead if you do not need to send a specific message.
+ Use Fl::awake(Fl_Awake_Handler, void*) or Fl::awake_once(Fl_Awake_Handler, void*)
+ if you need to send a message to the main thread and ensure that all messages
+ are processed in the order they were sent.
+
+ \see \ref advanced_multithreading
+ \see Fl::awake()
+ \see Fl::awake(Fl_Awake_Handler, void*)
+ \see Fl::awake_once(Fl_Awake_Handler, void*)
+*/
void Fl::awake(void *v) {
Fl::system_driver()->awake(v);
}
+/**
+ \brief Schedules a callback to be executed by the main thread, then wakes up the main thread.
+
+ This function lets a worker thread request that a specific callback function
+ be run by the main thread, passing optional user data. The callback will be
+ executed during the main thread's next event handling cycle.
+
+ The queue holding the list of handlers is limited to 1024 entries.
+ If the queue is full, the function will return -1 and the callback will not be
+ scheduled. However the main thread will still be woken up to process any
+ other pending events.
+
+ \note If user_data points to dynamically allocated memory, it is the
+ responsibility of the caller to ensure that the memory is valid until the
+ callback is executed. The callback will be executed during the main thread's
+ next event handling cycle, but depending on the sytems load, this may take
+ several seconds.
+
+ \return 0 if the callback was successfully scheduled
+ \return -1 if the queue is full.
+
+ \see Fl::awake()
+ \see Fl::awake_once(Fl_Awake_Handler, void*)
+ \see \ref advanced_multithreading
+*/
+int Fl::awake(Fl_Awake_Handler handler, void *user_data) {
+ int ret = Fl_System_Driver::push_awake_handler(handler, user_data, false);
+ Fl::awake();
+ return ret;
+}
+
+/**
+ \brief Schedules a callback to be executed once by the main thread, then wakes up the main thread.
+
+ This function lets a worker thread request that a specific callback function
+ be run by the main thread, passing optional user data. If a callback with the
+ same user_data is already scheduled, the previous entry will be removed and
+ the new entry will be appended to the list.
+
+ \return 0 if the callback was successfully scheduled
+ \return -1 if the queue is full.
+
+ \see Fl::awake()
+ \see Fl::awake(Fl_Awake_Handler, void*)
+ \see \ref advanced_multithreading
+*/
+int Fl::awake_once(Fl_Awake_Handler handler, void *user_data) {
+ // TODO: remove any previous entry with the same handler and user_data
+ int ret = Fl_System_Driver::push_awake_handler(handler, user_data, true);
+ Fl::awake();
+ return ret;
+}
+
+/**
+ \brief Returns the last message sent by a child thread.
+
+ \deprecated Use Fl::awake(Fl_Awake_Handler, void*) or
+ Fl::awake_once(Fl_Awake_Handler, void*) instead.
+
+ The thread_message() method returns the last message
+ that was sent from a child by the Fl::awake(void*) method.
+
+ This method is deprecated. The API can not ensure that Fl::thread_message()
+ returns the messages sent by Fl::awake(void *v) complete and in the correct
+ order.
+
+ \see \ref advanced_multithreading
+ \see Fl::awake()
+ \see Fl::awake(Fl_Awake_Handler, void*)
+ \see Fl::awake_once(Fl_Awake_Handler, void*)
+*/
void* Fl::thread_message() {
return Fl::system_driver()->thread_message();
}
+
+/**
+ \brief Acquire the global UI lock for FLTK.
+
+ The lock() method blocks the current thread until it
+ can safely access FLTK widgets and data. Child threads should
+ call this method prior to updating any widgets or accessing
+ data. The main thread must call Fl::lock() once before any windows are shown
+ to initialize the threading support in FLTK. The initial Fl::lock() call
+ will return non-zero if threading is not available on the platform.
+
+ Child threads enclose calls to FLTK functions between Fl::lock() and
+ Fl::unlock() accessing FLTK. When a child thread has finshed accessing FLTK
+ and wants the main thread to update the UI, it should call Fl::awake().
+
+ Child threads can never create, show, or hide windows.
+
+ When the wait() method is waiting
+ for input or timeouts, child threads are given access to FLTK.
+ Similarly, when the main thread needs to do processing, it will
+ wait until all child threads have called unlock() before processing
+ additional data.
+
+ \return 0 if threading is available on the platform; non-zero
+ otherwise.
+
+ \see \ref advanced_multithreading
+ \see Fl::lock()
+ \see Fl::awake()
+*/
int Fl::lock() {
return Fl::system_driver()->lock();
}
-void Fl::unlock() {
- Fl::system_driver()->unlock();
-}
+/**
+ \brief Release the global UI lock set by Fl::lock().
-#ifndef FL_DOXYGEN
+ The unlock() method releases the lock that was set using the lock() method.
+ Child threads should call this method as soon as they are finished
+ accessing FLTK.
-bool Fl_System_Driver::awake_ring_empty() {
- Fl::system_driver()->lock_ring();
- bool retval = (Fl::awake_ring_head_ == Fl::awake_ring_tail_);
- Fl::system_driver()->unlock_ring();
- return retval;
+ \see \ref advanced_multithreading
+ \see Fl::lock()
+*/
+void Fl::unlock() {
+ Fl::system_driver()->unlock();
}
-#endif // FL_DOXYGEN
diff --git a/src/Fl_win32.cxx b/src/Fl_win32.cxx
index 84cb6649d..a1a9ae39c 100644
--- a/src/Fl_win32.cxx
+++ b/src/Fl_win32.cxx
@@ -350,7 +350,7 @@ MSG fl_msg;
static void process_awake_handler_requests(void) {
Fl_Awake_Handler func;
void *data;
- while (Fl::get_awake_handler_(func, data) == 0) {
+ while (Fl::pop_awake_handler(func, data) == 0) {
func(data);
}
}
diff --git a/src/drivers/Posix/Fl_Posix_System_Driver.cxx b/src/drivers/Posix/Fl_Posix_System_Driver.cxx
index c4f2dc63c..4b8a14d79 100644
--- a/src/drivers/Posix/Fl_Posix_System_Driver.cxx
+++ b/src/drivers/Posix/Fl_Posix_System_Driver.cxx
@@ -387,7 +387,7 @@ static void thread_awake_cb(int fd, void*) {
}
Fl_Awake_Handler func;
void *data;
- while (Fl::get_awake_handler_(func, data)==0) {
+ while (Fl_System_Driver::pop_awake_handler(func, data)==0) {
(*func)(data);
}
}
diff --git a/test/threads.cxx b/test/threads.cxx
index f7349a668..f234ac7b2 100644
--- a/test/threads.cxx
+++ b/test/threads.cxx
@@ -87,10 +87,8 @@ extern "C" void* prime_func(void* p)
Fl::unlock();
// Send a message to the main thread, at which point it will
- // process any pending redraws for our browser widget. The
- // message we pass here isn't used for anything, so we could also
- // just pass NULL.
- Fl::awake(p);
+ // process any pending redraws for our browser widget.
+ Fl::awake();
if (n>10000 && !proud) {
proud = 1;
Fl::awake(magic_number_cb, value);