4.7 KiB
Multi-threading
Cross-platform threading primitives and synchronization mechanisms for concurrent programming.
Thread Pool Operations
Use ThreadPoolLite::Queue and ThreadPoolLite::QueueLambda for thread pool execution.
Use static functions ThreadPoolLite::Queue or ThreadPoolLite::QueueLambda to run a function in another thread.
Use TaskQueue when work must be queued from multiple threads but executed by one blocking task loop.
ThreadPoolLite Benefits
The thread pool provides several advantages over manual thread creation:
- Automatic thread lifecycle management
- Resource pooling to avoid thread creation overhead
- Better system resource utilization
- Simplified concurrent programming model
Usage Examples
// Queue a function pointer
ThreadPoolLite::Queue(someFunction);
// Queue a lambda expression
ThreadPoolLite::QueueLambda([]() {
// Work to be done in background thread
});
Task Queue Operations
TaskQueue owns a FIFO task list and a semaphore-backed blocking loop. Call QueueTask from any thread, run RunTaskQueue on the thread that should execute the work, and call QueueExitTask to leave the loop after already queued tasks are complete.
auto queue = Ptr(new TaskQueue);
auto thread = Ptr(Thread::CreateAndStart([=]()
{
queue->RunTaskQueue();
}, false));
queue->QueueTask([]()
{
// Runs on the task queue thread
});
queue->QueueExitTask();
Use TaskQueue instead of ThreadPoolLite when the order and single-threaded execution context of tasks matters.
Thread Control Operations
Thread Pausing
Use Thread::Sleep for thread pausing.
Use static function Thread::Sleep to pause the current thread for some milliseconds.
Thread Identification
Use Thread::GetCurrentThreadId for thread identification.
Use static function Thread::GetCurrentThreadId to get an identifier for the OS native thread running the current function.
Manual Thread Creation
Use Thread::CreateAndStart only when thread pool is insufficient.
Thread::CreateAndStart could be used to run a function in another thread while returning a Thread* to control it, but this is not recommended.
Always use ThreadPoolLite if possible. Use TaskQueue when a long-lived single-threaded task loop is required.
When to Use Manual Threads
Manual thread creation should only be considered when:
- You need fine-grained control over thread lifecycle
- Thread-specific configurations are required
- The thread pool doesn't meet specific timing requirements
- Working with legacy code that requires direct thread handles
Thread Pool with Synchronization
A ThreadPoolLite call with an EventObject is a better version of Thread::Wait. TaskQueue is the alternative when multiple callers should enqueue work to one owner thread.
This approach provides better resource management and avoids the complexities of manual thread synchronization.
Extra Content
Threading Best Practices
- Prefer Thread Pool: Use
ThreadPoolLitefor most concurrent operations, and useTaskQueuefor serialized owner-thread work - Avoid Thread Creation: Manual thread creation adds overhead and complexity
- Use Synchronization Primitives: Combine threading with proper synchronization objects
- Handle Exceptions: Ensure proper exception handling in threaded code
Performance Considerations
- Thread Pool: Minimal overhead for task scheduling and execution
- Thread Creation: Significant overhead for creating and destroying threads
- Context Switching: Consider the cost of frequent context switches
- Resource Contention: Be aware of shared resource access patterns
Cross-Platform Considerations
The threading API provides a unified interface across platforms:
- Windows: Uses Windows thread API internally
- Linux: Uses pthread API internally
- Behavior remains consistent across platforms
- Thread IDs are platform-specific but the API is uniform
Memory Management
- Thread pool automatically manages worker threads
- Manual threads require explicit cleanup
- Lambda captures should be carefully managed for thread safety
- Avoid capturing references to stack variables in threaded lambdas
Error Handling
Threading operations can fail due to:
- System resource exhaustion
- Permission restrictions
- Platform-specific limitations
Always check return values and handle potential failures gracefully.
Integration with Synchronization
Multi-threading works best when combined with appropriate synchronization primitives:
- Use with
Mutexfor cross-process synchronization - Combine with
CriticalSectionfor in-process protection - Integrate with
EventObjectfor thread coordination - Apply
ConditionVariablefor complex waiting scenarios