Comparing Synchronization Data Structures to the Windows API
This topic compares the behavior of the synchronization data structures that are provided by the Concurrency Runtime to those provided by the Windows API.
The synchronization data structures that are provided by the Concurrency Runtime follow the cooperative threading model. In the cooperative threading model, synchronization primitives explicitly yield their processing resources to other threads. This differs from the preemptive threading model, where processing resources are transferred to other threads by the controlling scheduler or operating system.
critical_section
The concurrency::critical_section class resembles the Windows CRITICAL_SECTION
structure because it can be used only by the threads of one process. For more information about critical sections in the Windows API, see Critical Section Objects.
reader_writer_lock
The concurrency::reader_writer_lock class resembles Windows slim reader/writer (SRW) locks. The following table explains the similarities and differences.
Feature | reader_writer_lock class |
SRW lock |
---|---|---|
Non-reentrant | Yes | Yes |
Can promote a reader to a writer (upgrade support) | No | No |
Can demote a writer to a reader (downgrade support) | No | No |
Write-preference lock | Yes | No |
FIFO access to writers | Yes | No |
For more information about SRW locks, see Slim Reader/Writer (SRW) Locks in the Platform SDK.
event
The concurrency::event class resembles an unnamed, Windows manual-reset event. However, an event
object behaves cooperatively, whereas a Windows event behaves preemptively. For more information about Windows events, see Event Objects.
Example
Description
To better understand the difference between the event
class and Windows events, consider the following example. This example enables the scheduler to create at most two simultaneous tasks and then calls two similar functions that use the event
class and a Windows manual-reset event. Each function first creates several tasks that wait for a shared event to become signaled. Each function then yields to the running tasks and then signals the event. Each function then waits for the signaled event.
Code
// event-comparison.cpp
// compile with: /EHsc
#include <windows.h>
#include <concrtrm.h>
#include <ppl.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
// Demonstrates the usage of cooperative events.
void RunCooperativeEvents()
{
// An event object.
event e;
// Create a task group and execute five tasks that wait for
// the event to be set.
task_group tasks;
for (int i = 0; i < 5; ++i)
{
tasks.run([&] {
// Print a message before waiting on the event.
wstringstream ss;
ss << L"\t\tContext " << GetExecutionContextId()
<< L": waiting on an event." << endl;
wcout << ss.str();
// Wait for the event to be set.
e.wait();
// Print a message after the event is set.
ss = wstringstream();
ss << L"\t\tContext " << GetExecutionContextId()
<< L": received the event." << endl;
wcout << ss.str();
});
}
// Wait a sufficient amount of time for all tasks to enter
// the waiting state.
Sleep(1000L);
// Set the event.
wstringstream ss;
ss << L"\tSetting the event." << endl;
wcout << ss.str();
e.set();
// Wait for all tasks to complete.
tasks.wait();
}
// Demonstrates the usage of preemptive events.
void RunWindowsEvents()
{
// A Windows event object.
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("Windows Event"));
// Create a task group and execute five tasks that wait for
// the event to be set.
task_group tasks;
for (int i = 0; i < 5; ++i)
{
tasks.run([&] {
// Print a message before waiting on the event.
wstringstream ss;
ss << L"\t\tContext " << GetExecutionContextId()
<< L": waiting on an event." << endl;
wcout << ss.str();
// Wait for the event to be set.
WaitForSingleObject(hEvent, INFINITE);
// Print a message after the event is set.
ss = wstringstream();
ss << L"\t\tContext " << GetExecutionContextId()
<< L": received the event." << endl;
wcout << ss.str();
});
}
// Wait a sufficient amount of time for all tasks to enter
// the waiting state.
Sleep(1000L);
// Set the event.
wstringstream ss;
ss << L"\tSetting the event." << endl;
wcout << ss.str();
SetEvent(hEvent);
// Wait for all tasks to complete.
tasks.wait();
// Close the event handle.
CloseHandle(hEvent);
}
int wmain()
{
// Create a scheduler policy that allows up to two
// simultaneous tasks.
SchedulerPolicy policy(1, MaxConcurrency, 2);
// Attach the policy to the current scheduler.
CurrentScheduler::Create(policy);
wcout << L"Cooperative event:" << endl;
RunCooperativeEvents();
wcout << L"Windows event:" << endl;
RunWindowsEvents();
}
Comments
This example produces the following sample output:
Cooperative event:
Context 0: waiting on an event.
Context 1: waiting on an event.
Context 2: waiting on an event.
Context 3: waiting on an event.
Context 4: waiting on an event.
Setting the event.
Context 5: received the event.
Context 6: received the event.
Context 7: received the event.
Context 8: received the event.
Context 9: received the event.
Windows event:
Context 10: waiting on an event.
Context 11: waiting on an event.
Setting the event.
Context 12: received the event.
Context 14: waiting on an event.
Context 15: received the event.
Context 16: waiting on an event.
Context 17: received the event.
Context 18: waiting on an event.
Context 19: received the event.
Context 13: received the event.
Because the event
class behaves cooperatively, the scheduler can reallocate processing resources to another context when an event is waiting to enter the signaled state. Therefore, more work is accomplished by the version that uses the event
class. In the version that uses Windows events, each waiting task must enter the signaled state before the next task is started.
For more information about tasks, see Task Parallelism.