下面我将为您介绍在 C++ 中使用 CriticalSection
实现线程同步的攻略。
什么是 CriticalSection
CriticalSection
是一种线程同步机制,它的目的是为了保证多线程环境下对共享变量的读写操作的正确性,防止出现竞争条件导致的数据错误。
在 C++ 中,CriticalSection
是由 Windows API 提供的一种同步对象,主要通过对共享变量的访问进行加锁和解锁操作来实现线程同步。
应用 CriticalSection
实现线程同步的攻略
要使用 CriticalSection
实现线程同步,一般需要按照下面的步骤进行操作:
- 定义一个
CriticalSection
对象来保护要访问的共享变量,如下所示:
cpp
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
上面的代码定义了一个 CriticalSection
对象 cs
,并初始化它。
- 在访问共享变量之前,先锁定
CriticalSection
对象,以防止其他线程对共享变量进行访问,如下所示:
cpp
EnterCriticalSection(&cs);
上面的代码用 EnterCriticalSection
函数锁定了 cs
对象,这会阻止其他线程对共享变量的访问,直到该锁被解除为止。
-
对共享变量进行读写操作。
-
在访问完成后,解锁
CriticalSection
对象,以允许其他线程对共享变量进行访问,如下所示:
cpp
LeaveCriticalSection(&cs);
上面的代码用 LeaveCriticalSection
函数解锁了 cs
对象,这会允许其他线程对共享变量进行访问。
- 最后,当不再需要使用
CriticalSection
对象时,应该删除它以释放资源,如下所示:
cpp
DeleteCriticalSection(&cs);
上面的代码用 DeleteCriticalSection
函数删除了 cs
对象,这会释放 cs
对象占用的资源。
示例说明
下面通过两个示例说明应用 CriticalSection
实现线程同步的方法。
示例 1:使用 CriticalSection
实现线程安全的计数器
假设有多个线程同时对一个计数器进行加一操作,我们需要确保在任意时刻只有一个线程在访问计数器,以避免出现竞争条件。
定义如下的计数器类 Counter
,使用 CriticalSection
来保护计数器的访问:
class Counter {
public:
Counter() {
InitializeCriticalSection(&cs);
count = 0;
}
~Counter() {
DeleteCriticalSection(&cs);
}
void increment() {
EnterCriticalSection(&cs);
++count;
LeaveCriticalSection(&cs);
}
int getCount() const {
EnterCriticalSection(&cs);
int result = count;
LeaveCriticalSection(&cs);
return result;
}
private:
CRITICAL_SECTION cs;
int count;
};
上面的代码中,increment
和 getCount
方法都使用了 CriticalSection
来保护计数器的访问。increment
方法先锁定 cs
对象以防止其他线程访问计数器,然后对计数器进行加一操作,最后解锁 cs
对象允许其他线程访问计数器。getCount
方法也是类似的通过锁定 cs
对象来获取计数器的值,然后解锁 cs
对象允许其他线程访问计数器。
接下来,我们可以在多个线程中创建一个 Counter
对象,并通过调用 increment
方法来对计数器进行加一操作,如下所示:
Counter counter;
DWORD WINAPI IncrementThreadProc(LPVOID lpParam) {
for (int i = 0; i < 10000; ++i) {
counter.increment();
}
return 0;
}
int main() {
HANDLE threads[10] = {NULL};
for (int i = 0; i < 10; ++i) {
threads[i] = CreateThread(NULL, 0, IncrementThreadProc, NULL, 0, NULL);
}
WaitForMultipleObjects(10, threads, TRUE, INFINITE);
for (int i = 0; i < 10; ++i) {
CloseHandle(threads[i]);
}
printf("Counter value is: %d\n", counter.getCount());
return 0;
}
上面的示例中,我们创建了 10 个线程,每个线程都会对计数器进行 10000 次加一操作。最后,我们调用 getCount
方法来获取计数器的值(理论上应该是 100000),并输出到控制台上。
示例 2:使用 CriticalSection
实现线程安全的任务队列
假设我们需要一个线程安全的任务队列,可以让多个线程往队列中添加任务,并让一个单独的线程从队列中取出任务并执行。
定义如下的任务队列类 TaskQueue
,使用 CriticalSection
来保护队列访问:
class TaskQueue {
public:
TaskQueue() {
InitializeCriticalSection(&cs);
}
~TaskQueue() {
DeleteCriticalSection(&cs);
}
void push(std::function<void()> task) {
EnterCriticalSection(&cs);
tasks.push(task);
LeaveCriticalSection(&cs);
}
std::function<void()> pop() {
EnterCriticalSection(&cs);
if (tasks.empty()) {
LeaveCriticalSection(&cs);
return nullptr;
}
std::function<void()> task = tasks.front();
tasks.pop();
LeaveCriticalSection(&cs);
return task;
}
private:
CRITICAL_SECTION cs;
std::queue<std::function<void()>> tasks;
};
上面的代码中,push
方法用于向任务队列中添加一个任务,pop
方法用于从队列中取出一个任务并返回。这两个方法都需要使用 CriticalSection
来保护队列的读写操作。push
和 pop
方法都有一个类似的逻辑,首先锁定 cs
对象,然后对队列进行读写操作,最后解锁 cs
对象以允许其他线程访问队列。
接下来,我们可以在多个线程中创建一个 TaskQueue
对象,并通过调用 push
方法往队列中添加任务,然后创建一个单独的线程来循环调用 pop
方法取出任务并执行,如下所示:
TaskQueue taskQueue;
DWORD WINAPI ProducerThreadProc(LPVOID lpParam) {
for (int i = 0; i < 10; ++i) {
taskQueue.push([]{ printf("Hello from producer thread!\n"); });
}
return 0;
}
DWORD WINAPI ConsumerThreadProc(LPVOID lpParam) {
while (true) {
std::function<void()> task = taskQueue.pop();
if (task == nullptr) {
Sleep(10);
continue;
}
task();
}
return 0;
}
int main() {
HANDLE producerThread = CreateThread(NULL, 0, ProducerThreadProc, NULL, 0, NULL);
HANDLE consumerThread = CreateThread(NULL, 0, ConsumerThreadProc, NULL, 0, NULL);
WaitForSingleObject(producerThread, INFINITE);
WaitForSingleObject(consumerThread, INFINITE);
CloseHandle(producerThread);
CloseHandle(consumerThread);
return 0;
}
上面的示例中,我们先创建了一个 TaskQueue
对象,然后启动了一个生产者线程和一个消费者线程。生产者线程会往队列中添加 10 个任务,每个任务都是打印一句话。消费者线程会循环调用 pop
方法从队列中取出任务并执行,直到队列为空为止。当生产者线程和消费者线程都执行完毕后,我们关闭这两个线程的句柄并退出 main 函数。
总结
上面的攻略就是使用 CriticalSection
实现线程同步的完整流程。使用 CriticalSection
可以有效地保护共享变量的读写操作,避免出现竞争条件。通过以上两个示例,我们可以更好地理解如何应用 CriticalSection
来实现线程安全的代码。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++使用CriticalSection实现线程同步实例 - Python技术站