条件变量使用细节

2022-12-26 15:32:03 浏览数 (2)

c 服务器开发精髓有感

消费者和生产者模式,创建5个消费者,一个生产者,生产者每隔一秒生产一个任务,通知所有消费者去处理

代码语言:javascript复制
#include <Windows.h>
#include <iostream>
#include <list>

class Task
{
public:
    Task(int taskID)
    {
        this->taskID = taskID;
    }

    void doTask()
    {
        std::cout << "handle a task, taskID: " << taskID << ", threadID: " << GetCurrentThreadId() << std::endl;
    }

private:
    int taskID;
};

CRITICAL_SECTION    myCriticalSection;
CONDITION_VARIABLE  myConditionVar;
std::list<Task*>    tasks;

DWORD WINAPI consumerThread(LPVOID param)
{
    Task* pTask = NULL;
    while (true)
    {
        //进入临界区
        EnterCriticalSection(&myCriticalSection);
        while (tasks.empty())
        {
            //如果SleepConditionVariableCS挂起,挂起前会离开临界区,不往下执行。
            //当发生变化后,条件合适,SleepConditionVariableCS将直接进入临界区。
            SleepConditionVariableCS(&myConditionVar, &myCriticalSection, INFINITE);
        }

        pTask = tasks.front();
        tasks.pop_front();

        //SleepConditionVariableCS被唤醒后进入临界区,
        //为了让其他线程有机会操作tasks,这里需要再次离开临界区
        LeaveCriticalSection(&myCriticalSection);

        if (pTask == NULL)
            continue;

        pTask->doTask();
        delete pTask;
        pTask = NULL;
    }

    return 0;
}

DWORD WINAPI producerThread(LPVOID param)
{
    int taskID = 0;
    Task* pTask = NULL;

    while (true)
    {
        pTask = new Task(taskID);

        //进入临界区
        EnterCriticalSection(&myCriticalSection);
        tasks.push_back(pTask);
        std::cout << "produce a task, taskID: " << taskID << ", threadID: " << GetCurrentThreadId() << std::endl;

        LeaveCriticalSection(&myCriticalSection);

        WakeConditionVariable(&myConditionVar);

        taskID  ;

        //休眠1秒
        Sleep(1000);
    }

    return 0;
}

int main()
{
    InitializeCriticalSection(&myCriticalSection);
    InitializeConditionVariable(&myConditionVar);

    //创建5个消费者线程
    HANDLE consumerThreadHandles[5];
    for (int i = 0; i < 5;   i)
    {
        consumerThreadHandles[i] = CreateThread(NULL, 0, consumerThread, NULL, 0, NULL);
    }

    //创建一个生产者线程
    HANDLE producerThreadHandle = CreateThread(NULL, 0, producerThread, NULL, 0, NULL);

    //等待生产者线程退出
    WaitForSingleObject(producerThreadHandle, INFINITE);

    //等待消费者线程退出
    for (int i = 0; i < 5;   i)
    {
        WaitForSingleObject(consumerThreadHandles[i], INFINITE);
    }

    DeleteCriticalSection(&myCriticalSection);

    return 0;
}

CRITICAL_SECTION的使用

这里使用CRITICAL_SECTION而不是Mutex的原因是CRITICAL_SECTION不是内核级的互斥体,更快一些,两者有如下区别:

CRITICAL_SECTION

Mutex

性能和速度

快。Critical Section本身不是内核对象,相关函数(EnterCriticalSection,eaveCriticalSection)只有当想要获得的锁正好被别的线程拥有时才会退化成和Mutex一样,即转换到内核模式,发费600个左右的 CPU指令周期。

慢。Mutex 是内核对象,相关函数的执行 (WaitForSingleObject,ReleaseMutex)需要用户模式(User Mode)到内核模式(Kernel Mode)的转换

能否跨越进程(Process)边界

进入临界区/加锁

EnterCriticalSection

lock

离开临界区/释放

LeaveCriticalSection

unlock

条件变量的虚拟唤醒

代码语言:javascript复制
while (tasks.empty())
        {
            //如果SleepConditionVariableCS挂起,挂起前会离开临界区,不往下执行。
            //当发生变化后,条件合适,SleepConditionVariableCS将直接进入临界区。
            SleepConditionVariableCS(&myConditionVar, &myCriticalSection, INFINITE);
        }

这里使用while而不是if的原因有两个,一个数所谓的虚拟唤醒,一个是逻辑上就需要

对于虚拟唤醒这个问题,其实是一个内核底层的问题,出现虚拟唤醒的原因是底层为了不错过信号而产生的,这里就不深入探讨,也不需要,这不是用户层能改变的。

第二个,如果不使用while,那如果判断完了不就往下走了,这不符合。

https://blog.csdn.net/llmblcwwmm/article/details/106820773

0 人点赞