C++使用CriticalSection实现线程同步实例

下面我将为您介绍在 C++ 中使用 CriticalSection 实现线程同步的攻略。

什么是 CriticalSection

CriticalSection 是一种线程同步机制,它的目的是为了保证多线程环境下对共享变量的读写操作的正确性,防止出现竞争条件导致的数据错误。

在 C++ 中,CriticalSection 是由 Windows API 提供的一种同步对象,主要通过对共享变量的访问进行加锁和解锁操作来实现线程同步。

应用 CriticalSection 实现线程同步的攻略

要使用 CriticalSection 实现线程同步,一般需要按照下面的步骤进行操作:

  1. 定义一个 CriticalSection 对象来保护要访问的共享变量,如下所示:

cpp
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);

上面的代码定义了一个 CriticalSection 对象 cs,并初始化它。

  1. 在访问共享变量之前,先锁定 CriticalSection 对象,以防止其他线程对共享变量进行访问,如下所示:

cpp
EnterCriticalSection(&cs);

上面的代码用 EnterCriticalSection 函数锁定了 cs 对象,这会阻止其他线程对共享变量的访问,直到该锁被解除为止。

  1. 对共享变量进行读写操作。

  2. 在访问完成后,解锁 CriticalSection 对象,以允许其他线程对共享变量进行访问,如下所示:

cpp
LeaveCriticalSection(&cs);

上面的代码用 LeaveCriticalSection 函数解锁了 cs 对象,这会允许其他线程对共享变量进行访问。

  1. 最后,当不再需要使用 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;
};

上面的代码中,incrementgetCount 方法都使用了 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 来保护队列的读写操作。pushpop 方法都有一个类似的逻辑,首先锁定 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技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • 逍遥自在学C语言 位运算符 “|” 的5种高级用法

    前言 在上一篇文章中,我们介绍了&运算符的高级用法,本篇文章,我们将介绍| 运算符的一些高级用法。 一、人物简介 第一位闪亮登场,有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 —— 逍遥。 二、将两个字节合并成一个16位整数 #include <stdio.h> int main() { uns…

    C语言 2023年4月17日
    00
  • C语言指针算术运算

    下面是对“C语言指针算术运算”的详细讲解: 一、C语言指针算术运算简介 C语言中,指针算术运算指的是对指向某个数据类型对象的指针进行加减运算的过程。运算的结果是指针类型的值,指向新的地址,这个新的地址是运算前指针地址和运算对象的数据类型大小的乘积(单位是字节)所形成的。 C语言中的指针算术运算具有如下两条规则: 指针类型和加减对象的类型必须是一致的。 对指针…

    C 2023年5月9日
    00
  • C语言 文件的打开与关闭详解及示例代码

    下面我将详细讲解“C语言 文件的打开与关闭详解及示例代码”的完整攻略。 一、文件的打开 文件的打开可以使用stdio.h库中提供的fopen函数实现。fopen函数的原型如下: FILE* fopen(const char* filename, const char* mode); 其中filename是文件名,mode是打开的模式,它们都是以字符串形式传递…

    C 2023年5月24日
    00
  • Oracle实现行转换成列的方法

    实现行转换成列是很实用的功能,在Oracle中可以使用PIVOT关键字实现。下面是具体步骤: 步骤一:创建表和插入数据 首先,我们需要创建一个表并插入一些数据。这些数据的格式应该是需要被转换的,也就是需要转换成列。 我们创建一个表名为sales,包括以下列:product,year和amount。并向其中插入一些数据。 CREATE TABLE sales …

    C 2023年5月22日
    00
  • 详解Java中NullPointerException异常的原因详解以及解决方法

    详解Java中NullPointerException异常的原因以及解决方法 异常原因 Java中的NullPointerException异常通常指程序在试图使用空引用时抛出的异常。这通常出现在以下三种情况: 当你尝试调用一个空对象的方法时,例如: String str = null; int length = str.length(); // 抛出Nul…

    C 2023年5月22日
    00
  • CMakeList中自动编译protobuf文件过程

    当使用Protobuf数据交换格式时,我们需要将.proto文件编译为相应的C++类才能在代码中使用它们。CMake是常用的构建工具之一,它具有内置的支持来自动生成Protobuf源代码。 以下是在CMakeList中自动编译protobuf文件的完整攻略: 步骤 1:从Google官网下载Protobuf 要在CMakeList中自动编译protobuf文…

    C 2023年5月23日
    00
  • 小米4c怎么样?小米4c搭载骁龙808和Type-C

    当谈到小米4c时,我们需要关注它的配置和性能。小米4c主打设计良好且价格亲民的特点,它的主要优势在于骁龙808处理器和Type-C接口。 小米4c搭载骁龙808处理器 小米4c搭载了骁龙808处理器,它是高通推出的一款六核心处理器,其中两个大核心时钟频率高达1.8GHz,剩下的四个小核心时钟频率为1.4GHz。 骁龙808处理器采用了Adreno 418 G…

    C 2023年5月23日
    00
  • C语言 switch-case语句

    以下是C语言 switch-case语句的完整使用攻略: 什么是switch-case语句? C语言中的switch-case语句是一种用于多分支条件判断的语句,它可以根据不同的取值来执行不同的代码块。switch语句会根据一个表达式的值与case关键字后面的值进行匹配,如果匹配成功,则会执行与之对应的代码块。当匹配失败时,可以使用default关键字来执行…

    C 2023年5月9日
    00
合作推广
合作推广
分享本页
返回顶部