summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlbrecht Schlosser <albrechts.fltk@online.de>2025-06-22 16:30:21 +0200
committerAlbrecht Schlosser <albrechts.fltk@online.de>2025-06-22 16:46:44 +0200
commit088d98389cdc4c0ed38d05e4a8e59fab88198515 (patch)
tree3190a8cc102a0cbcf91d668487b8130aa79b63f8
parentacd77fa8dc415a32b7a33996b5b01b10a69c2463 (diff)
Improve threads demo test/threads.cxx (#1263)
- Replace Fl_Browser with Fl_Terminal which uses a constant buffer size - Don't lock the GUI for every single prime. Collect primes for at least 0.25 seconds before calling Fl::awake(handler, buffer) - Use (two) alternate buffers for collecting prime data. - Use Fl::lock() *only* to protect thread data at initialization time. Observation on Debian 12, CPU: 12-core, 12th Gen Intel Core i7-1260P: speedup > factor 4, using multiple cores, GUI fully functional: scrolling the display, resizing, ... Tested natively (X11 + Wayland) and cross-compiled for Windows, using `wine`.
-rw-r--r--test/threads.cxx166
1 files changed, 108 insertions, 58 deletions
diff --git a/test/threads.cxx b/test/threads.cxx
index f234ac7b2..3412bb08a 100644
--- a/test/threads.cxx
+++ b/test/threads.cxx
@@ -1,7 +1,7 @@
//
// Threading example program for the Fast Light Tool Kit (FLTK).
//
-// Copyright 1998-2018 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
@@ -19,77 +19,123 @@
#if defined(HAVE_PTHREAD) || defined(_WIN32)
# include <FL/Fl.H>
# include <FL/Fl_Double_Window.H>
-# include <FL/Fl_Browser.H>
+# include <FL/Fl_Terminal.H>
# include <FL/Fl_Value_Output.H>
# include <FL/fl_ask.H>
# include "threads.h"
# include <stdio.h>
# include <math.h>
+# include <vector>
+
+// min. time in seconds before calling Fl::awake(...)
+#define DELTA 0.25
+
+// struct to collect primes until at least <DELTA> seconds passed.
+// Two such structs per thread are used as alternate buffers.
+
+struct prime {
+ int idx; // thread index: 0 or {1..6}
+ int done; // set to 1 after it was worked on
+ Fl_Terminal *terminal; // widget to write output to
+ Fl_Value_Output *value; // highest prime
+ std::vector<unsigned long> primes; // collected primes within time frame
+};
Fl_Thread prime_thread;
-Fl_Browser *browser1, *browser2;
+Fl_Terminal *tty1, *tty2;
Fl_Value_Output *value1, *value2;
int start2 = 3;
-void magic_number_cb(void *p)
-{
+void magic_number_cb(void *p) {
Fl_Value_Output *w = (Fl_Value_Output*)p;
w->labelcolor(FL_RED);
w->redraw_label();
}
-extern "C" void* prime_func(void* p)
-{
- Fl_Browser* browser = (Fl_Browser*) p;
+// This is called indirectly by Fl::awake(update_handler, (void *)prime)
+// in the context of the main (FLTK GUI) thread.
+
+void update_handler(void *v) {
+ struct prime *pr = (struct prime *)v;
+ for (auto n : pr->primes) {
+ pr->terminal->printf("prime: %10u\n", n);
+ if (n > pr->value->value())
+ pr->value->value(n);
+ }
+ pr->done = 1;
+}
+
+extern "C" void* prime_func(void* p) {
+ Fl_Terminal* terminal = (Fl_Terminal*)p;
Fl_Value_Output *value;
- int n;
+ unsigned long n;
int step;
char proud = 0;
- if (browser == browser2) {
- n = start2;
+ // initialize thread variables
+
+ if (terminal == tty2) { // multiple threads
+ Fl::lock(); // lock to prevent race condition on `start2`
+ n = start2;
start2 += 2;
- step = 12;
- value = value2;
- } else {
- n = 3;
- step = 2;
- value = value1;
+ Fl::unlock();
+ step = 12;
+ value = value2;
+ } else { // single thread
+ n = 3;
+ step = 2;
+ value = value1;
}
+ // initialize alternate buffers (struct prime) to store primes
+
+ struct prime pr[2];
+ pr[0].done = 0;
+ pr[1].done = 1;
+ pr[0].terminal = pr[1].terminal = terminal;
+ pr[0].idx = pr[1].idx = n/2 - 1;
+ pr[0].value = pr[1].value = value;
+ pr[0].primes.clear();
+ pr[1].primes.clear();
+ int pi = 0; // prime buffer index
+
+ Fl_Timestamp last = Fl::now();
+
// very simple prime number calculator !
//
// The return at the end of this function can never be reached and thus
// will generate a warning with some compilers, however we need to have
// a return statement or other compilers will complain there is no return
- // statement. To avoid warnings on all compilers, we fool the smart ones
- // into beleiving that there is a chance that we reach the end by testing
- // n>=0, knowing that logically, n will never be negative in this context.
- if (n>=0) for (;;) {
+ // statement. To avoid warnings on all compilers, we fool the smart ones into
+ // believing that there is a chance that we reach the end by testing n > 0,
+ // knowing that logically, n will never be less than 3 in this context.
+
+ if (n > 0) for (;;) {
int pp;
int hn = (int)sqrt((double)n);
- for (pp=3; pp<=hn; pp+=2) if ( n%pp == 0 ) break;
- if (pp >= hn) {
- char s[128];
- snprintf(s, 128, "%d", n);
+ for (pp = 3; pp <= hn; pp += 2) if ( n%pp == 0 ) break;
- // Obtain a lock before we access the browser widget...
- Fl::lock();
-
- browser->add(s);
- browser->bottomline(browser->size());
- if (n > value->value()) value->value(n);
- n += step;
+ if (pp > hn) { // n is a prime
- // Release the lock...
- Fl::unlock();
+ pr[pi].primes.push_back(n);
// Send a message to the main thread, at which point it will
- // process any pending redraws for our browser widget.
- Fl::awake();
- if (n>10000 && !proud) {
+ // process any pending updates.
+
+ double ssl = Fl::seconds_since(last);
+
+ if (ssl > DELTA && pr[1-pi].done) { // ready to switch buffers
+ last = Fl::now();
+ Fl::awake(update_handler, (void *)(&pr[pi]));
+ pi = 1 - pi; // switch to alternate buffer
+ pr[pi].primes.clear(); // clear primes
+ }
+
+ n += step;
+
+ if (n > 5*1000*1000 && !proud) {
proud = 1;
Fl::awake(magic_number_cb, value);
}
@@ -107,40 +153,44 @@ extern "C" void* prime_func(void* p)
int main(int argc, char **argv)
{
- Fl_Double_Window* w = new Fl_Double_Window(200, 200, "Single Thread");
- browser1 = new Fl_Browser(0, 0, 200, 175);
- w->resizable(browser1);
+ // First window: single thread
+ Fl_Double_Window* w = new Fl_Double_Window(300, 200, "Single Thread");
+ tty1 = new Fl_Terminal(0, 0, 300, 175);
+ w->resizable(tty1);
value1 = new Fl_Value_Output(100, 175, 200, 25, "Max Prime:");
w->end();
w->show(argc, argv);
- w = new Fl_Double_Window(200, 200, "Six Threads");
- browser2 = new Fl_Browser(0, 0, 200, 175);
- w->resizable(browser2);
+
+ // Second window: multiple threads
+ w = new Fl_Double_Window(300, 200, "Six Threads");
+ tty2 = new Fl_Terminal(0, 0, 300, 175);
+ w->resizable(tty2);
value2 = new Fl_Value_Output(100, 175, 200, 25, "Max Prime:");
w->end();
w->show();
- browser1->add("Prime numbers:");
- browser2->add("Prime numbers:");
+ tty1->printf("Prime numbers:\n");
+ tty2->printf("Prime numbers:\n");
+
+ // Enable multi-thread support by locking from the main thread.
+ // Fl::wait() and Fl::run() call Fl::unlock() and Fl::lock() as needed
+ // to release control to the child threads when it is safe to do so...
- // Enable multi-thread support by locking from the main
- // thread. Fl::wait() and Fl::run() call Fl::unlock() and
- // Fl::lock() as needed to release control to the child threads
- // when it is safe to do so...
Fl::lock();
// Start threads...
- // One thread displaying in one browser
- fl_create_thread(prime_thread, prime_func, browser1);
+ // One thread displaying in one terminal
+ fl_create_thread(prime_thread, prime_func, tty1);
+
+ // Six threads displaying in another terminal
- // Several threads displaying in another browser
- fl_create_thread(prime_thread, prime_func, browser2);
- fl_create_thread(prime_thread, prime_func, browser2);
- fl_create_thread(prime_thread, prime_func, browser2);
- fl_create_thread(prime_thread, prime_func, browser2);
- fl_create_thread(prime_thread, prime_func, browser2);
- fl_create_thread(prime_thread, prime_func, browser2);
+ fl_create_thread(prime_thread, prime_func, tty2);
+ fl_create_thread(prime_thread, prime_func, tty2);
+ fl_create_thread(prime_thread, prime_func, tty2);
+ fl_create_thread(prime_thread, prime_func, tty2);
+ fl_create_thread(prime_thread, prime_func, tty2);
+ fl_create_thread(prime_thread, prime_func, tty2);
Fl::run();