diff options
| -rw-r--r-- | FL/Fl.H | 33 | ||||
| -rw-r--r-- | documentation/src/advanced.dox | 62 | ||||
| -rw-r--r-- | src/Fl_System_Driver.H | 14 | ||||
| -rw-r--r-- | src/Fl_lock.cxx | 302 | ||||
| -rw-r--r-- | src/Fl_win32.cxx | 2 | ||||
| -rw-r--r-- | src/drivers/Posix/Fl_Posix_System_Driver.cxx | 2 | ||||
| -rw-r--r-- | test/threads.cxx | 6 |
7 files changed, 247 insertions, 174 deletions
@@ -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); |
