Code: Select all
; PureBasic example using Windows API Condition Variables
Import "Kernel32.lib"
InitializeConditionVariable(*ConditionVariable)
InitializeCriticalSection(*CriticalSection)
EnterCriticalSection(*CriticalSection)
LeaveCriticalSection(*CriticalSection)
SleepConditionVariableCS(*ConditionVariable, *CriticalSection, dwMilliseconds)
WakeConditionVariable(*ConditionVariable)
DeleteCriticalSection(*CriticalSection)
EndImport
; Define the CONDITION_VARIABLE structure
Structure CONDITION_VARIABLE
Reserved.q[2] ; 16 bytes for 64-bit systems
EndStructure
; Structure to hold both CONDITION_VARIABLE and CRITICAL_SECTION
Structure CONDITION_VAR
cv.CONDITION_VARIABLE
cs.CRITICAL_SECTION
EndStructure
; Structure to pass to the thread
Structure THREAD_PARAMS
*condition.CONDITION_VAR
*variable
EndStructure
; Thread procedure
Procedure ThreadFunction(*params.THREAD_PARAMS)
; Enter critical section
EnterCriticalSection(@*params\condition\cs)
; Wait for the condition to be met
While PeekL(*params\variable) = 10
; Wait on the condition variable
SleepConditionVariableCS(@*params\condition\cv, @*params\condition\cs, #INFINITE)
Wend
; The condition has been met, continue execution
Debug "Variable changed! New value: " + Str(PeekL(*params\variable))
; Leave critical section
LeaveCriticalSection(@*params\condition\cs)
ProcedureReturn 0
EndProcedure
; Main program
Procedure Main()
; Local variable in main thread
Protected g_variable = 10
; Initialize condition and critical section structures
Protected condition.CONDITION_VAR
InitializeConditionVariable(@condition\cv)
InitializeCriticalSection(@condition\cs)
; Initialize thread parameters
Protected params.THREAD_PARAMS
params\condition = @condition
params\variable = @g_variable
; Create a thread and pass the params structure
hThread = CreateThread_(#Null, 0, @ThreadFunction(), @params, 0, #Null)
; Simulate some work in the main thread
Delay(2000)
; Enter critical section
EnterCriticalSection(@condition\cs)
; Change the variable
g_variable = 42
; Signal the condition variable to wake up the waiting thread
WakeConditionVariable(@condition\cv)
; Leave critical section
LeaveCriticalSection(@condition\cs)
; Wait for the thread to finish
WaitForSingleObject_(hThread, #INFINITE)
; Cleanup
CloseHandle_(hThread)
DeleteCriticalSection(@condition\cs)
EndProcedure
Main()
End
Condition variables and semaphores are both synchronization primitives used in multithreading, but they have different purposes and are used in different ways. Here's a breakdown of the key differences between them:
### 1. **Purpose and Concept**
- **Condition Variables:**
- **Purpose:** Condition variables are used to block a thread until a specific condition is met. They are typically used with a mutex (or critical section) to safely wait for changes to a shared resource.
- **Concept:** A thread waits on a condition variable if a condition is not met, releasing the associated mutex. When another thread changes the shared state and signals the condition variable, the waiting thread is woken up and must re-acquire the mutex before continuing.
- **Semaphores:**
- **Purpose:** Semaphores are used to control access to a limited number of resources or to enforce limits on the number of threads that can access a resource at the same time.
- **Concept:** A semaphore maintains an internal counter. When a thread acquires (waits on) a semaphore, the counter is decremented. When a thread releases (signals) the semaphore, the counter is incremented. If the counter is zero, threads that try to acquire the semaphore will block until the counter is increased.
### 2. **Synchronization Mechanism**
- **Condition Variables:**
- **Mutex Requirement:** Condition variables are always used with a mutex or critical section. The mutex is held while checking the condition and is released when the thread waits on the condition variable.
- **Signaling:** When the condition variable is signaled (by `signal` or `broadcast`), one or more waiting threads are woken up. They must re-acquire the mutex before continuing.
- **Semaphores:**
- **Counting Mechanism:** A semaphore’s counter represents the number of resources available. The semaphore allows a thread to proceed if the counter is greater than zero (decrementing the counter) or blocks the thread if the counter is zero.
- **Binary vs. Counting:** A binary semaphore (where the counter can only be 0 or 1) is similar to a mutex. A counting semaphore allows more flexibility, representing multiple instances of a resource.
### 3. **Typical Use Cases**
- **Condition Variables:**
- **Producer-Consumer Patterns:** Condition variables are ideal for scenarios where threads need to wait for specific conditions, such as when producers and consumers must coordinate access to a shared buffer.
- **Event Notification:** Used when threads need to wait for an event or condition, such as waiting for a queue to become non-empty before consuming an item.
- **Semaphores:**
- **Resource Management:** Semaphores are commonly used to manage access to a limited number of resources, such as a pool of database connections or file handles.
- **Task Synchronization:** Semaphores can also be used to synchronize tasks, ensuring that a certain number of tasks are completed before others proceed.
### 4. **Blocking Behavior**
- **Condition Variables:**
- **Condition-Based Blocking:** A thread blocks if a specific condition is not true. The condition variable allows the thread to sleep until another thread modifies the condition and signals the variable.
- **Re-checking the Condition:** After waking up, the thread typically re-checks the condition because the wake-up might have been caused by a spurious wake-up or another thread consuming the resource.
- **Semaphores:**
- **Count-Based Blocking:** A thread blocks if the semaphore's counter is zero. The semaphore decrements the counter when a thread acquires it and increments the counter when a thread releases it.
- **Direct Blocking:** The semaphore’s counter directly determines if a thread should block or proceed.
### 5. **Thread Safety**
- **Condition Variables:**
- **Mutex Dependency:** The thread safety of condition variables depends on the associated mutex. The mutex ensures that the shared state is safely checked and modified before the condition variable is waited on or signaled.
- **Shared Condition Handling:** Condition variables are designed to handle complex conditions involving multiple threads, ensuring that only the thread(s) that meet the condition proceed.
- **Semaphores:**
- **Counter-Based:** Semaphores rely on their internal counter to manage thread access. They do not require an external mutex to function, though a mutex might be used in combination with a semaphore to protect shared resources.
- **Resource Management:** Semaphores inherently provide thread safety for managing access to a fixed number of resources.
### 6. **Efficiency and Flexibility**
- **Condition Variables:**
- **Fine-Grained Control:** Condition variables provide fine-grained control over thread synchronization, allowing threads to wait precisely for certain conditions, but they require careful use of mutexes to avoid deadlocks.
- **Spurious Wake-ups:** Condition variables might experience spurious wake-ups, requiring threads to re-check the condition upon waking.
- **Semaphores:**
- **Simpler and Direct:** Semaphores are simpler to use for controlling access to resources and do not require the same level of coordination as condition variables. They are efficient for scenarios involving fixed resource management.
- **Less Fine-Grained:** Semaphores do not provide as fine-grained control as condition variables. They work well for simple counting problems but are not designed for complex condition-based synchronization.
### Summary
- **Condition Variables** are more suitable for scenarios where threads need to wait for specific conditions on shared resources, often requiring complex coordination and fine-grained control over thread execution.
- **Semaphores** are better for scenarios where you need to limit access to a certain number of resources or synchronize a fixed number of threads, providing a simpler and more direct way to manage concurrency.