Documentation Index
Fetch the complete documentation index at: https://mintlify.com/fltk/fltk/llms.txt
Use this file to discover all available pages before exploring further.
What This Example Demonstrates
This example shows proper multithreading with FLTK:
- Creating and managing threads
- Thread-safe communication with GUI (Fl::lock/unlock)
- Using Fl::awake() to update GUI from worker threads
- Background processing without blocking the UI
- Displaying results in real-time
- Thread synchronization patterns
FLTK’s threading support allows background work while keeping the UI responsive.
Complete Source Code
Source file: test/threads.cxx (simplified prime number calculator)
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Terminal.H>
#include <FL/Fl_Output.H>
#include <stdio.h>
#include <math.h>
#include <vector>
#include "threads.h" // Platform-specific: pthread.h or Windows threads
#define DELTA 0.25 // Min seconds before updating GUI
// Data structure for passing results to GUI thread
struct prime {
int idx;
int done;
Fl_Terminal *terminal;
unsigned long max_prime;
Fl_Output *value;
std::vector<unsigned long> primes;
};
Fl_Terminal *tty1, *tty2;
Fl_Output *value1, *value2;
int start2 = 3;
void magic_number_cb(void *p) {
Fl_Output *w = (Fl_Output*)p;
w->labelcolor(FL_RED);
w->parent()->redraw();
}
// Called in GUI thread via Fl::awake()
void update_handler(void *v) {
char max_buf[32];
struct prime *pr = (struct prime *)v;
// Display all collected primes
for (auto n : pr->primes) {
pr->terminal->printf("prime: %10lu\n", n);
if (n > pr->max_prime)
pr->max_prime = n;
}
snprintf(max_buf, sizeof(max_buf), "%9lu", pr->max_prime);
pr->value->value(max_buf);
pr->done = 1;
}
// Worker thread function
extern "C" void* prime_func(void* p) {
Fl_Terminal* terminal = (Fl_Terminal*)p;
Fl_Output *value;
unsigned long n;
unsigned long max_value = 0;
int step;
// Initialize thread variables
if (terminal == tty2) { // Multiple threads
Fl::lock(); // Lock to prevent race condition
n = start2;
start2 += 2;
Fl::unlock();
step = 12;
value = value2;
} else { // Single thread
n = 3;
step = 2;
value = value1;
}
// Initialize alternate buffers
struct prime pr[2];
pr[0].idx = pr[1].idx = ((int)n/2 - 1);
pr[0].done = 0;
pr[1].done = 1;
pr[0].terminal = pr[1].terminal = terminal;
pr[0].max_prime = pr[1].max_prime = 0;
pr[0].value = pr[1].value = value;
pr[0].primes.clear();
pr[1].primes.clear();
int pi = 0; // Buffer index
Fl_Timestamp last = Fl::now();
// Calculate prime numbers
for (;;) {
int pp;
int hn = (int)sqrt((double)n);
// Test if n is prime
for (pp = 3; pp <= hn; pp += 2) {
if (n % pp == 0)
break;
}
if (pp > hn) { // n is prime
pr[pi].primes.push_back(n);
double ssl = Fl::seconds_since(last);
// Send update to GUI thread periodically
if (ssl > DELTA && pr[1-pi].done) {
last = Fl::now();
Fl::awake(update_handler, (void *)(&pr[pi]));
pi = 1 - pi; // Switch to alternate buffer
pr[pi].primes.clear();
}
if (n > max_value) {
pr[pi].max_prime = n;
}
n += step;
if (n > 5 * 1000 * 1000) {
Fl::awake(magic_number_cb, value);
}
} else {
// Thread safety for shared variables
Fl::lock();
n += step;
Fl::unlock();
}
}
return 0L;
}
// Close callback
void close_cb(Fl_Widget *w, void *v) {
Fl::hide_all_windows();
printf("Max prime with 1 thread : %s\n", value1->value());
printf("Max prime with 6 threads: %s\n", value2->value());
}
int main(int argc, char **argv) {
// First window: single thread
Fl_Double_Window* w1 = new Fl_Double_Window(200, 200, "Single Thread");
tty1 = new Fl_Terminal(0, 0, 200, 175);
tty1->color(FL_BACKGROUND2_COLOR);
w1->resizable(tty1);
value1 = new Fl_Output(100, 175, 98, 23, "Max Prime:");
value1->textfont(FL_COURIER);
w1->callback(close_cb);
w1->end();
w1->show(argc, argv);
// Second window: multiple threads
Fl_Double_Window* w2 = new Fl_Double_Window(200, 200, "Six Threads");
tty2 = new Fl_Terminal(0, 0, 200, 175);
tty2->color(FL_BACKGROUND2_COLOR);
w2->resizable(tty2);
value2 = new Fl_Output(100, 175, 98, 23, "Max Prime:");
value2->textfont(FL_COURIER);
w2->callback(close_cb);
w2->end();
w2->show();
tty1->printf("Prime numbers:\n");
tty2->printf("Prime numbers:\n");
// Enable multi-thread support
Fl::lock();
// Start threads
Fl_Thread prime_thread;
// One thread for window 1
fl_create_thread(prime_thread, prime_func, tty1);
// Six threads for window 2
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();
return 0;
}
Compilation Command
# Linux/Unix
fltk-config --compile threads.cxx -lpthread
# Windows
fltk-config --compile threads.cxx
# Or with CMake
cmake . && make threads
Expected Behavior
- Two windows appear side by side
- Left window: single thread calculating primes
- Right window: six threads calculating primes in parallel
- Prime numbers scroll in terminal widgets
- “Max Prime” output shows highest prime found
- Right window finds primes ~6x faster
- Label turns red when reaching 5 million
Key Concepts
Enable Threading
// MUST call before creating threads
Fl::lock();
// Then start your event loop
Fl::run();
// Fl::run() automatically calls Fl::unlock() and Fl::lock() as needed
Thread Safety Rules
CRITICAL RULES:
- Never call FLTK GUI functions directly from worker threads
- Use Fl::awake() to communicate with the GUI thread
- Call Fl::lock() before accessing shared data
- Call Fl::unlock() after accessing shared data
Creating Threads
#include "threads.h" // FLTK's cross-platform thread header
// Declare thread handle
Fl_Thread my_thread;
// Thread function signature
extern "C" void* my_thread_func(void* data) {
// Thread code here
return NULL;
}
// Create and start thread
fl_create_thread(my_thread, my_thread_func, user_data);
Communicating from Thread to GUI
// In worker thread:
void* worker_thread(void* data) {
// Do background work
int result = compute_something();
// Send result to GUI thread
Fl::awake(gui_update_callback, &result);
return NULL;
}
// In GUI thread (called by FLTK event loop):
void gui_update_callback(void* data) {
int* result = (int*)data;
widget->value(*result);
widget->redraw();
}
Protecting Shared Data
int shared_counter = 0; // Shared between threads
void* thread_func(void* data) {
for (int i = 0; i < 1000; i++) {
Fl::lock(); // Lock before accessing
shared_counter++; // Modify shared data
Fl::unlock(); // Unlock after
}
return NULL;
}
Simple Threading Example
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Progress.H>
#include <FL/Fl_Box.H>
#include "threads.h"
#include <unistd.h> // for sleep()
Fl_Progress *progress;
Fl_Box *status;
volatile bool thread_running = false;
// GUI update function (called in main thread)
void update_progress(void* data) {
int* value = (int*)data;
progress->value(*value);
char buf[32];
snprintf(buf, sizeof(buf), "Progress: %d%%", *value);
status->copy_label(buf);
}
// Worker thread
extern "C" void* worker_thread(void* data) {
for (int i = 0; i <= 100; i += 10) {
// Simulate work
sleep(1);
// Update GUI via awake
static int progress_val;
progress_val = i;
Fl::awake(update_progress, &progress_val);
}
thread_running = false;
return NULL;
}
void start_cb(Fl_Widget* w, void*) {
if (!thread_running) {
thread_running = true;
Fl_Thread thread;
fl_create_thread(thread, worker_thread, NULL);
((Fl_Button*)w)->deactivate();
}
}
int main(int argc, char** argv) {
Fl_Window win(300, 150, "Thread Progress");
status = new Fl_Box(10, 10, 280, 30, "Click Start");
progress = new Fl_Progress(10, 50, 280, 30);
progress->minimum(0);
progress->maximum(100);
progress->value(0);
Fl_Button *btn = new Fl_Button(100, 100, 100, 30, "Start");
btn->callback(start_cb);
win.end();
win.show(argc, argv);
Fl::lock(); // Enable threading
return Fl::run();
}
Advanced Patterns
Thread Pool
class WorkQueue {
std::vector<Fl_Thread> threads;
std::queue<Task*> tasks;
bool shutdown;
public:
void start(int num_threads) {
shutdown = false;
for (int i = 0; i < num_threads; i++) {
Fl_Thread t;
fl_create_thread(t, worker_thread, this);
threads.push_back(t);
}
}
void add_task(Task* task) {
Fl::lock();
tasks.push(task);
Fl::unlock();
}
static void* worker_thread(void* data) {
WorkQueue* queue = (WorkQueue*)data;
while (!queue->shutdown) {
Task* task = NULL;
Fl::lock();
if (!queue->tasks.empty()) {
task = queue->tasks.front();
queue->tasks.pop();
}
Fl::unlock();
if (task) {
task->execute();
delete task;
} else {
usleep(10000); // Sleep 10ms if no work
}
}
return NULL;
}
};
Cancellable Thread
volatile bool cancel_requested = false;
void* cancellable_thread(void* data) {
for (int i = 0; i < 1000000 && !cancel_requested; i++) {
// Do work
compute(i);
// Check cancellation periodically
if (i % 1000 == 0 && cancel_requested) {
break;
}
}
Fl::awake(thread_finished_cb, NULL);
return NULL;
}
void cancel_button_cb(Fl_Widget*, void*) {
cancel_requested = true;
}
Progress Reporting
struct ProgressData {
int current;
int total;
char message[256];
};
void progress_callback(void* data) {
ProgressData* p = (ProgressData*)data;
progress_bar->value(100.0 * p->current / p->total);
status_label->copy_label(p->message);
delete p; // Clean up
}
void* processing_thread(void* data) {
int total = 1000;
for (int i = 0; i < total; i++) {
process_item(i);
if (i % 10 == 0) {
ProgressData* p = new ProgressData;
p->current = i;
p->total = total;
snprintf(p->message, sizeof(p->message),
"Processing item %d of %d", i, total);
Fl::awake(progress_callback, p);
}
}
return NULL;
}
Common Pitfalls
DON’T: Call GUI functions from threads
// WRONG - will crash!
void* bad_thread(void* data) {
Fl_Box* box = (Fl_Box*)data;
box->label("Updated"); // DON'T DO THIS
box->redraw(); // DON'T DO THIS
return NULL;
}
DO: Use Fl::awake()
// CORRECT
void update_label(void* data) {
Fl_Box* box = (Fl_Box*)data;
box->label("Updated");
box->redraw();
}
void* good_thread(void* data) {
// Do background work...
// Update GUI safely
Fl::awake(update_label, data);
return NULL;
}
Memory Management
// Allocate data that awake callback will free
void* worker(void* data) {
int* result = new int(42); // Allocate
Fl::awake(callback, result);
return NULL;
}
void callback(void* data) {
int* result = (int*)data;
printf("Result: %d\n", *result);
delete result; // Free
}
// FLTK provides cross-platform threading:
#ifdef _WIN32
// Uses Windows threads
#else
// Uses POSIX threads (pthread)
#endif
// Thread handle type
Fl_Thread thread;
// Create thread (cross-platform)
fl_create_thread(thread, function, data);
Best Practices
- Always call Fl::lock() before starting threads
- Use Fl::awake() for all GUI updates from threads
- Minimize lock duration - lock, modify, unlock quickly
- Check for platform support - threading requires pthread or Windows
- Use volatile for flags checked by multiple threads
- Clean up threads before exit (use join or detach)
- Test thoroughly - threading bugs are hard to reproduce
- Use debugging tools - Valgrind, thread sanitizers
Debugging Tips
// Add thread ID to debug output
#include <pthread.h>
printf("Thread %ld: doing work\n", (long)pthread_self());
// Detect deadlocks
if (!Fl::lock()) {
fprintf(stderr, "Lock failed!\n");
}
// Verify you're in GUI thread
if (Fl::thread_id() == fl_thread_id()) {
printf("In GUI thread\n");
}
Next Steps