diff options
| author | Albrecht Schlosser <albrechts.fltk@online.de> | 2025-06-22 16:30:21 +0200 |
|---|---|---|
| committer | Albrecht Schlosser <albrechts.fltk@online.de> | 2025-06-22 16:46:44 +0200 |
| commit | 088d98389cdc4c0ed38d05e4a8e59fab88198515 (patch) | |
| tree | 3190a8cc102a0cbcf91d668487b8130aa79b63f8 /test/threads.cxx | |
| parent | acd77fa8dc415a32b7a33996b5b01b10a69c2463 (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`.
Diffstat (limited to 'test/threads.cxx')
| -rw-r--r-- | test/threads.cxx | 166 |
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(); |
