zoukankan      html  css  js  c++  java
  • 第8章 用户模式下的线程同步(4)_条件变量(Condition Variable)

    8.6 条件变量(Condition Variables)——可利用临界区或SRWLock锁来实现

    8.6.1 条件变量的使用

    (1)条件变量机制就是为了简化 “生产者-消费者”问题而设计的一种线程同步机制。其目的让线程以原子方式释放锁并将自己阻塞,直到某一个条件成立为止。如读者线程当没有数据可读取时,则应释放锁并等待,直到写者线程产生了新的数据。同理,当写者把数据结构写满时,那么写者应该释放SRWLock并等待,直到读者把数据结构清空。

    (2)等待函数:SleepConditionVariableCS/SleepConditionVariableSRW。其功能类似于事件对象:WaitForSingleObject(hEvent,INFINITE);

    参数

    描述

    pConditionVariable

    己初始化的条件变量,调用线程正在等待该条件变量

    pCriticalSection/pSRWLock

    指向临界区或SRWLock的指针,用来同步对共享资源的访问

    dwMilliSeconds

    等待条件变量被触发(不是锁)的时间

    0:只是用来测试,并立即返回。

    INFINITE:无限等待条件变量(不是锁),直到它被触发

    其它值:如果超时时,则不再等待,直接返回。返回值为FALSE

    Flags

    使用SRWLock才有这个参数,表示一旦条件变量被触发,将于何种方式得到锁,如共享或排它。对于写者线程传入0,即获得一个排它锁。对于读者线程传入CONDITION_VARIABLE_LOCKMODE_SHARDED,表示共享锁。

    (3)唤醒线程:WakeConditionVariable或WakeAllConditionVariable(阻塞在Sleep*函数中的线程),其参数只有一个,就是条件变量。其功能类似于SetEvent将事件设为有信号状态。

      ①WakeConditionVariable唤醒一个在Sleep*函数中的线程以便让其获得锁。如消费者消费完数据,可以唤醒一个生产者来生产数据。因为生产者获得的是排他锁,所以一次只需唤醒一个

      ②当调用WakeAllConditionVariable时,会唤醒Sleep*函数中的多个线程。如当生产者产生好数据后,可以唤醒所有的消费者,因为这些消费者可共享锁,所以可以同时唤醒。

      ③注意Wake*函数唤醒的是那些被Sleep*函数阻塞的线程,而当调用RleaseSRWLock*后,唤醒的是那些阻塞在AcquireSRWLock*函数中的线程,这两者是不同的。

    (4)条件变量使用的一般模式

      //消费者线程函数

      DWORD WINAPI Consumer(PVOID pvParam)
      {
         AcquireSRWLockShared(&g_srwLock); //请求共享锁(读锁)
         ……//消费之前的一些操作。然后等待条件变量,如果不满足,会释放锁,并开始等待。
         SleepConditionVariableSRW(&g_cvConsume,&g_srwLock,INFINITE, //等待消费者条件变量
                                CONDITION_VARIABLE_LOCKMODE_SHARED); //等待条件变量,会被生产者唤醒
                                                                     //当条件满足时会重新获得锁(共享)
           …… //消费
    
          ReleaseSRWLockShared(&g_srwLock); //释放锁
          WakeConditionVariable(&g_cvProduce);//唤醒一个生产者线程,触发生产者条件变量。因为生产者使用排他锁,所以每次只需唤醒一个线程。
    }

    //生产者线程函数

    DWORD WINAPI Producer(PVOID pvParam)  
     {
         AcquireSRWLockExclusive(&g_srwLock); //请求排他锁(读锁)
    
         ……  //生产数据之前的一些操作,然后等待条件变量,如果不满足,则释放锁,并开始等待。
    
         //等待条件变量,会被消费者线程唤醒
         SleepConditionVariableSRW(&g_cvProduce,&g_srwLock,INFINITE,0); //等待重新获得排他锁
    
           …… //生产
    
          ReleaseSRWLockExclusive(&g_srwLock); //释放排他锁
          WakeAllConditionVariable(&g_cvProduce);//触发消费者条件变量,唤醒所有Sleep*函数中的消费者,因为消费者会通过共享锁共享数据,所以可同时唤醒。
    }

    【Queue程序】使用SRWLock和条件变量演示生产者和消费者问题

    效果图

    ★★程序的几个说明★★

      ①初始化时,会创建4个客户线程(写入者)和两个服务线程(读者),每个客户线程会在队列末尾添加一个请求元素,并更新客户列表信息。因为写入线程多,读者线程少,所以队列很快就会被塞满,这里会在客户列表中会有操作失败的提示。

      ②客户列表框每项显示的内容为客户线程(是个索引号,4个线程的索引值)添加的请求号(注意请求号会随着每次线程添加的请求而递增)。

      ③服务线程负责对请求处理,线程0处理奇数号请求,线程1处理偶数号请求。在元素被添加到队列之前,两个线程会处理请求状态,当条件变量满足时会被唤醒起来处理客户的请求号,并标记为己读。同时当队列己满时,客户线程也会进行睡眠,当条件变量满足时,被服务线程唤醒起来工作,断续向队列中写入数据。

      ④当按下“停止”按钮时,会结束所有的客户线程和服务线程。

    //队列的实现——队列头文件

    #pragma once
    #include <windows.h>
    
    //////////////////////////////////////////////////////////////////////////
    //此队列并不具有线程安全性,由客户线程和服务线程自行访问的同步问题
    class CQueue{
    public:
        //队列中元素的类型
        //当某个客户线程请求每一次会将其线程ID和请求号记录在队列中
        //本例假设中,请求号是这样给的:每个线程每次发送请求时,请求号是
        //递增的。如线程1,第1次会发送1号请求,第2次发送2号请求,依此类推
        struct ELEMENT{
            int m_nThreadNum; //客户线程号
            int m_nRequestNum;//请求号
            //其他元素放这以后
        };
        typedef ELEMENT* PELEMENT;
    
    private:
        struct INNER_ELEMENT{
            int m_nStamp;  //0表示该元素是空元素。
            //记录该元素加入队列时的顺序(即属于第几个加入队列中的元素了)
            ELEMENT m_element;
        };
        typedef INNER_ELEMENT* PINNER_ELEMENT;
    
    private:
        PINNER_ELEMENT m_pElements;  //队列元素数组,数组长度设计为固定,这就是需要保护的数据
        int            m_nMaxElements;  //数组长度。CQueue构造时数组初始长度
        int            m_nCurrentStamp; //跟踪当前插入的元素,添加元素时递增,且只增不减
    
    private:
        int GetFreeSlot();  //返回队列中第1个m_nStamp为0的元素
        int GetNextSlot(int nThreadNum); //返回需要服务线程nThreadNum处理的最早的请求
        //即属于服务线程要处理的请求中m_nStamp值最小的那项
    public:
        CQueue(int nMaxElements);
        ~CQueue();
    
        BOOL IsFull();   //队列是否己满
        BOOL IsEmpty(int nThreadNum); //队列是否存在指定线程nTreadNum要处理的请求
        void AddElement(ELEMENT e);
        BOOL GetNewElement(int nThreadNum, ELEMENT &e); //获取队列中需要线程nThreadNum处理的
        //最早插入的元素
    };

    //队列的实现——队列实现的文件Queue.cpp

    #include "Queue.h"
    
    // 构造函数
    CQueue::CQueue(int nMaxElements)
    {
        //分配固定大小的(nMaxElements个元素)队列,并初始化各元素
        m_pElements = (PINNER_ELEMENT)HeapAlloc(GetProcessHeap(), 0,
                                                sizeof(INNER_ELEMENT)* nMaxElements);
        ZeroMemory(m_pElements, sizeof(INNER_ELEMENT)*nMaxElements); //各元素初始化为0
    
        //初始化当前元素个数的计数值
        m_nCurrentStamp = 0;
    
        //保存队列元素的最大个数
        m_nMaxElements = nMaxElements;
    }
    
    //析构函数
    CQueue::~CQueue()
    {
        HeapFree(GetProcessHeap(), 0, m_pElements);
    }
    
    //判断队列是否己满
    BOOL CQueue::IsFull()
    {
        return (-1 == GetFreeSlot());
    }
    
    //队列是否存在指定线程nTreadNum要处理的请求
    BOOL CQueue::IsEmpty(int nThreadNum)
    {
        return (GetNextSlot(nThreadNum) == -1);
    }
    
    //获得队列中第1个空的元素(即m_nStamp=0,说明该元素为空或该元素己被读取过了
    //注意,己经读取过的也直接废弃,可以重新被新的请求覆盖使用)
    int CQueue::GetFreeSlot()
    {
        //查找第1个m_nStamp的元素
        for (int idx = 0; idx < m_nMaxElements; idx++)
        {
            if (m_pElements[idx].m_nStamp == 0)
                return idx;
        }
    
        //如果找不到元素(即队列己满),返回-1
        return (-1);
    }
    
    //返回需要服务线程nThreadNum处理的最早的请求
    //即属于服务线程要处理的请求中m_nStamp最小的那项
    int CQueue::GetNextSlot(int nThreadNum)
    {
        //默认没有需要服务线程nThreadNum处理的元素
        int firstSlot = -1;
    
        //m_nCurrentStamp为最后添加元素的stamp,为最大的stamp
        //因要在队列中查找属于线程nThreadNum处理的,且其m_nStamp值最小
        //因此根据查找最小值的方法,
        int minStamp = m_nCurrentStamp + 1;
    
        //线程nThreadNum为奇数时处理客户请求号为奇数的元素,为偶数时,处理偶数号请求
        for (int idx = 0; idx < m_nMaxElements; idx++){
            if ((m_pElements[idx].m_nStamp != 0) &&  //确保元素非空
                ((m_pElements[idx].m_element.m_nRequestNum % 2) == nThreadNum) && //需要该线程处理的请求
                (m_pElements[idx].m_nStamp < minStamp)){   //查找m_nStamp的最小值
                minStamp = m_pElements[idx].m_nStamp;
                firstSlot = idx;
            }
        }
    
        return (firstSlot);
    }
    
    //获取队列中需要线程nThreadNum处理的最早插入的元素
    BOOL CQueue::GetNewElement(int nThreadNum, ELEMENT &e)
    {
        int nNewSlot = GetNextSlot(nThreadNum);
        if (-1 == nNewSlot)
            return FALSE;
    
        //拷贝元素的内容
        e = m_pElements[nNewSlot].m_element;
    
        //将该元素标访为己读
        m_pElements[nNewSlot].m_nStamp = 0;
    
        return TRUE;
    }
    
    //增加请求(元素)
    void CQueue::AddElement(ELEMENT e)
    {
        //如果队列己满时,不能再增加,直接返回
        int nFreeSlot = GetFreeSlot();
        if (nFreeSlot == -1)
            return;
    
        //拷贝内容
        m_pElements[nFreeSlot].m_element = e;
    
        //记录一下该元素加入队列的顺序(即,属于第几个加入进来的元素了)
        m_pElements[nFreeSlot].m_nStamp = ++m_nCurrentStamp;
    }

    //主程序main.cpp

    #include "../../CommonFiles/CmnHdr.h"
    #include <tchar.h>
    #include <strsafe.h>
    #include "resource.h"
    #include "Queue.h"
    
    //////////////////////////////////////////////////////////////////////////
    CQueue                g_q(10);  //共享队列,共有10个元素
    volatile   LONG     g_bShutDown; //是否结束所有读、写者线程
    HWND                g_hWnd;   //供客户/服务线程使用
    SRWLOCK                g_srwLock; //读写锁,用于保护共享队列
    CONDITION_VARIABLE  g_cvReadyToConsume;  //写者发出信号,表示可以读取
    CONDITION_VARIABLE  g_cvReadyToProduce;  //读者发出信号,表示可以写入
    
    //所有读、写者线程的句柄(本例共6个线程:4个写者、2个写者)
    HANDLE g_hThreads[MAXIMUM_WAIT_OBJECTS]; //为了可扩展性,最多可以创建64个线程
    
    //读、写者线程的总数量
    int  g_nNumThreads = 0; //每创建一个线程,该值加1
    
    //////////////////////////////////////////////////////////////////////////
    void AddText(HWND hWndLB, PCTSTR pszFormat, ...)
    {
        va_list argList;
        va_start(argList, pszFormat);
    
        TCHAR sz[20 * 1024];
        _vstprintf_s(sz, _countof(sz), pszFormat, argList);
        ListBox_SetCurSel(hWndLB, ListBox_AddString(hWndLB, sz));
        va_end(argList);
    }
    
    //////////////////////////////////////////////////////////////////////////
    //读取队列中需要服务线程[nThreadNum]处理的最早的元素(每次读完后,最早的这个元素会被废弃),并显示在列表框中
    //该函数的第2个参数在本例中没用,被我注释掉了
    BOOL ConsumeElement(int nThreadNum, /*int nRequestNum,*/ HWND hWndLB)
    {
        // 以共享模式获得SRWLock,如果锁已经被写者线程以独占模式占用,函数会阻塞  
        // 如果锁已经被另一个读者线程以共享模式获得,运行对请求进行处理  
        AcquireSRWLockShared(&g_srwLock);
    
        // 如果队列不存在该读者线程(nThreadNum)处理的元素,则阻塞,等待一个写者产生新的元素  
        // 直到写者线程触发g_cvReadyToConsume条件变量为止  
        while (g_q.IsEmpty(nThreadNum) && !g_bShutDown){
            //没有可读的元素
            AddText(hWndLB, TEXT("服务线程[%d]没有可处理的数据!"), nThreadNum);
    
            //等待,直到写者线程将其唤醒(注意,写者线程只要写入数据,就会唤醒
            //这些等待读取数据的线程,让他们自行判断这些数据是不是属于自己的那部分。
            SleepConditionVariableSRW(&g_cvReadyToConsume, &g_srwLock, INFINITE, 
                        CONDITION_VARIABLE_LOCKMODE_SHARED);
        }
    
        //如果用户按了“结束”按钮
        if (g_bShutDown){
            //显示当前服务线程退出的信息
            AddText(hWndLB, TEXT("服务线程[%d]退出!"), nThreadNum);
    
            //唤醒另一个可能被阻塞的写者线程(因为共享锁,读者线程不会阻塞)
            ReleaseSRWLockShared(&g_srwLock); 
    
            //通知那些正在等待数据的其他读线程结束(即有部分读线程正在
            //阻塞在上面的Sleep*函数中等待数据的到达
            WakeConditionVariable(&g_cvReadyToConsume);//用来唤醒其他读线程!    
            return FALSE;    
        }
    
        CQueue::ELEMENT e;
    
        //获取队列中需要线程[nThreadNum]处理的最早插入的元素
        g_q.GetNewElement(nThreadNum, e);
    
        //释放共享锁
        ReleaseSRWLockShared(&g_srwLock);
    
        AddText(hWndLB, TEXT("服务线程[%d]处理了客户线程[%d]的元素%d"), 
                nThreadNum,e.m_nThreadNum,e.m_nRequestNum);
    
        //读完一个元素后队列中会产生一个空项,唤醒写者线程
        WakeConditionVariable(&g_cvReadyToProduce);
        return TRUE;
    }
    //读者线程函数(服务线程)
    DWORD WINAPI ReadThread(PVOID pParam)
    {
        int nThreadNum = PtrToUlong(pParam);
        HWND hWndLB = GetDlgItem(g_hWnd, IDC_SERVERS);
    
        //课本中nRequestNum这个变量不需要用到,该句改为如下的while语句
        //for (int nRequestNum = 1; !g_bShutDown;nRequestNum++){ 
        while (!g_bShutDown){
            if (!ConsumeElement(nThreadNum, /*nRequestNum,*/ hWndLB))
                return 0;
            Sleep(2500); //读取下一个元素时先休眠2.5秒
        }
        
        //结束线程(用户按下了“结束”按钮)
        AddText(hWndLB, TEXT("服务线程[%d]退出!"), nThreadNum);
        return 0;
    }
    
    //////////////////////////////////////////////////////////////////////////
    //写者线程函数(客户线程)
    DWORD WINAPI WriteThread(PVOID pParam)
    {
        int nThreadNum = PtrToUlong(pParam); //线程的索引号
        HWND hWndLB = GetDlgItem(g_hWnd, IDC_CLIENTS);
    
        for (int nRequestNum = 1; !g_bShutDown;nRequestNum++){
            
            //模拟写入数据,每次将自己的线程号及请求号保存到队列中去
            CQueue::ELEMENT e = { nThreadNum, nRequestNum };
    
            //以独占的方式获得SRWLock锁,如果调用线程获得锁后,其他任何申请锁的线程会被阻塞在
            //AcquireSRWLock*函数中,直到锁被释放。当调用线程申请排他锁时,若锁已被其他线程获得
            //则他们都释放锁以后才可申请到。
            AcquireSRWLockExclusive(&g_srwLock);
    
            //获得排它锁后,判断队列是否己满,如果己满,则挂起自己,直到条件变量满足
            //    注意在写入的过程中,可以会收到“结束”线程的命令
            if (g_q.IsFull() & !g_bShutDown){         
                //队列己满
                AddText(hWndLB, TEXT("线程[%d]检测到队列己满:不能增加元素%d"), 
                        nThreadNum,nRequestNum);
    
                //睡眠,等待读取者线程读取一个元素,以便腾出空间来容纳新元素
                //这里调用线程会交给锁,并挂起自己,直到读者线程读出元素,最后一个参数
                //为0,表示当被唤醒时,以排他方式再次获得共享锁
                SleepConditionVariableSRW(&g_cvReadyToProduce, &g_srwLock, INFINITE, 0);    
            }
    
            //如果用户按了结束按钮,则结束该线程
            if (g_bShutDown){
                //显示客户线程结束提示
                AddText(hWndLB, TEXT("客户线程[%d]退出"), nThreadNum);
    
                //释放锁,唤醒那些阻塞在Acuqire*函数中的线程
                ReleaseSRWLockExclusive(&g_srwLock);
    
                //唤醒那些被阻塞在Sleep*函数中的线程(这些线程已经获得锁了,当释放排他锁后被系统唤醒
                //而不是被Wake*函数唤醒),以便让其自然结束。
                WakeAllConditionVariable(&g_cvReadyToConsume);
    
                return 0; //结束自己        
            } else{
                //将新元素加入到队列中
                g_q.AddElement(e);
    
                //显示新加入的元素
                AddText(hWndLB, TEXT("客户线程[%d]添加元素%d"), nThreadNum, nRequestNum);
                
                //释放锁
                ReleaseSRWLockExclusive(&g_srwLock);
    
                //唤醒所有被阻塞的读者线程,让其自然结束
                WakeAllConditionVariable(&g_cvReadyToConsume);
    
                //在新增下一个元素前,休眠1.5秒
                Sleep(1500);
            }        
        }
    
        //当前线程退出
        AddText(hWndLB, TEXT("客户线程[%d]退出"), nThreadNum);
    
        return 0;
    }
    //////////////////////////////////////////////////////////////////////////
    //
    void StopProcessing()
    {
        if (!g_bShutDown){
    
            //请求所有的线程结束
            InterlockedExchange(&g_bShutDown, TRUE);
            
            //通知所有正在等待条件变量的线程结束
            WakeAllConditionVariable(&g_cvReadyToConsume);
            WakeAllConditionVariable(&g_cvReadyToProduce);
    
            //等待所有的线程
            WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
    
            //清除线程内核对象
            while (g_nNumThreads--)
                CloseHandle(g_hThreads[g_nNumThreads]);
    
            //关闭列表框
            AddText(GetDlgItem(g_hWnd, IDC_SERVERS), TEXT("-----------------------------------"));
            AddText(GetDlgItem(g_hWnd, IDC_CLIENTS), TEXT("-----------------------------------"));
        }
    }
    
    DWORD WINAPI StoppingThread(PVOID pParam)
    {
        StopProcessing();
        return 0;
    }
    
    
    //////////////////////////////////////////////////////////////////////////
    BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
    {
        chSETDLGICONS(hwnd, IDI_QUEUE);
    
        g_hWnd = hwnd; //用于让读、写者线程显示状态
    
        //初始化SRWLock锁
        InitializeSRWLock(&g_srwLock);
    
        //初始化条件变量
        InitializeConditionVariable(&g_cvReadyToConsume);
        InitializeConditionVariable(&g_cvReadyToProduce);
    
        //将被设为TRUE来结束读、写者线程
        g_bShutDown = FALSE;
    
        //创建4个写者线程(客户线程)
        DWORD dwThreadID;
        for (int x = 0; x < 4;x++){
            g_hThreads[g_nNumThreads++] =
                chBEGINTHREADEX(NULL, 0,
                                WriteThread,
                                (PVOID)(INT_PTR)x,//将x作为线程索引号
                                0, //立即运行
                                &dwThreadID);
        }
    
        //创建2个读者线程(服务线程)
        for (int x = 0; x < 2; x++){
            g_hThreads[g_nNumThreads++] =
                chBEGINTHREADEX(NULL, 0,
                ReadThread,
                (PVOID)(INT_PTR)x,//将x作为线程索引号
                0, //立即运行
                &dwThreadID);
        }
    
        return TRUE;
    }
    //////////////////////////////////////////////////////////////////////////
    void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtrl, UINT codeNotify)
    {
        switch (id)
        {
        case IDCANCEL:
            EndDialog(hWnd, id);
            break;
    
        case IDC_BTN_STOP:
            //StopProcessing不能在UI线程中调用,因为这个函数
            //里会的WaitForMultipleObjects会挂起UI线程,使得当子线程SendMessage
            //来清空列表框时会发生死锁,这里另开一个线程
            DWORD dwThreadID;
            CloseHandle(chBEGINTHREADEX(NULL, 0, StoppingThread, NULL, 0, &dwThreadID));
            
            //禁用“结束”按钮,防止被多次按下
            Button_Enable(hWndCtrl, FALSE);
            break;
        }
    }
    //////////////////////////////////////////////////////////////////////////
    INT_PTR WINAPI DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
            chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
        }
    
        return FALSE;
    }
    //////////////////////////////////////////////////////////////////////////
    int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int)
    {
        DialogBox(hInstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, DlgProc);
        StopProcessing();
        return 0;
    }

    //resource.h

    //{{NO_DEPENDENCIES}}
    // Microsoft Visual C++ 生成的包含文件。
    // 供 8_Queue.rc 使用
    //
    #define IDD_QUEUE                       101
    #define IDI_QUEUE                       102
    #define IDC_SERVERS                     1001
    #define IDC_CLIENTS                     1002
    #define IDC_BTN_STOP                    1003
    
    // Next default values for new objects
    // 
    #ifdef APSTUDIO_INVOKED
    #ifndef APSTUDIO_READONLY_SYMBOLS
    #define _APS_NEXT_RESOURCE_VALUE        103
    #define _APS_NEXT_COMMAND_VALUE         40001
    #define _APS_NEXT_CONTROL_VALUE         1004
    #define _APS_NEXT_SYMED_VALUE           101
    #endif
    #endif

    //资源文件Queue.rc

    // Microsoft Visual C++ generated resource script.
    //
    #include "resource.h"
    
    #define APSTUDIO_READONLY_SYMBOLS
    /////////////////////////////////////////////////////////////////////////////
    //
    // Generated from the TEXTINCLUDE 2 resource.
    //
    #include "winres.h"
    
    /////////////////////////////////////////////////////////////////////////////
    #undef APSTUDIO_READONLY_SYMBOLS
    
    /////////////////////////////////////////////////////////////////////////////
    // 中文(简体,中国) resources
    
    #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
    LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
    
    #ifdef APSTUDIO_INVOKED
    /////////////////////////////////////////////////////////////////////////////
    //
    // TEXTINCLUDE
    //
    
    1 TEXTINCLUDE 
    BEGIN
        "resource.h"
    END
    
    2 TEXTINCLUDE 
    BEGIN
        "#include ""winres.h""
    "
        ""
    END
    
    3 TEXTINCLUDE 
    BEGIN
        "
    "
        ""
    END
    
    #endif    // APSTUDIO_INVOKED
    
    
    /////////////////////////////////////////////////////////////////////////////
    //
    // Dialog
    //
    
    IDD_QUEUE DIALOGEX 0, 0, 393, 203
    STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
    CAPTION "队列"
    FONT 8, "MS Shell Dlg", 400, 0, 0x1
    BEGIN
        DEFPUSHBUTTON   "停止",IDC_BTN_STOP,172,182,50,14
        GROUPBOX        "客户线程",IDC_STATIC,14,15,178,163
        GROUPBOX        "服务线程",IDC_STATIC,201,15,178,163
        LISTBOX         IDC_SERVERS,207,25,166,151,NOT LBS_NOTIFY | WS_VSCROLL | WS_TABSTOP
        LISTBOX         IDC_CLIENTS, 19, 25, 166, 151, NOT LBS_NOTIFY | WS_VSCROLL | WS_TABSTOP
    END
    
    
    /////////////////////////////////////////////////////////////////////////////
    //
    // DESIGNINFO
    //
    
    #ifdef APSTUDIO_INVOKED
    GUIDELINES DESIGNINFO
    BEGIN
        IDD_QUEUE, DIALOG
        BEGIN
            LEFTMARGIN, 7
            RIGHTMARGIN, 386
            TOPMARGIN, 7
            BOTTOMMARGIN, 196
        END
    END
    #endif    // APSTUDIO_INVOKED
    
    
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDI_QUEUE               ICON                    "Queue.ico"
    #endif    // 中文(简体,中国) resources
    /////////////////////////////////////////////////////////////////////////////
    
    
    
    #ifndef APSTUDIO_INVOKED
    /////////////////////////////////////////////////////////////////////////////
    //
    // Generated from the TEXTINCLUDE 3 resource.
    //
    
    
    /////////////////////////////////////////////////////////////////////////////
    #endif    // not APSTUDIO_INVOKED

    8.6.2 一些有用的窍门和技巧

    (1)以原子方式操作一组对象时使用一个锁

      ①每个“逻辑”资源只使用一个锁来更新,如向链表中增加一个元素时,还要更新其计数值。这时元素和计数器应视为同一个单独的“逻辑”资源。同时不论对这个逻辑资源进行读或写时,都应该只使用一个锁。

      ②每个逻辑资源都应有自己锁。但不必为所有逻辑资源都创建单独的锁。这是因为任一时刻系统只允许一个线程执行。

    (2)同时访问多个逻辑资源——死锁的例子

    线程1

    线程2

    DWORD WINAP Thread1Func(…)

    {

       EnterCriticalSection(&g_csResource1);

       EnterCriticalSection(&g_csResource2);

       …… //从资源1中读取元素

       …… //将元素插入到资源2中

       LeaveCriticalSection(&g_csResource2);

       LeaveCriticalSection(&g_csResource1);

       return 0

    }

    DWORD WINAP Thread2Func(…)

    {

       EnterCriticalSection(&g_csResource2);

       EnterCriticalSection(&g_csResource1);

       …… //从资源1中读取元素

       …… //将元素插入到资源2中

       LeaveCriticalSection(&g_csResource1);

       LeaveCriticalSection(&g_csResource2);

       return 0

    }

    死锁的可能原因设线程1取得g_csResource1锁,然后执行执行线程2并且线程2取得g_csResource2锁。此时就会出现死锁,因为线程1在等资源2的锁,而线程2在等资源1的锁。

    解决方案:线程1和线程2都应以相同的顺序来获得资源的锁。如将线程2的第1行和第2行调换一下。注意,调用LeaveCriticalSection的顺序是无关紧要的。因为这个函数不会让线程挂起。

    (3)不要长时间占用锁

    线程1(可能占用锁时间较长)

    线程2(占用锁的时间较短)

    MYSTRUCT g_mySt;

    CRITICAL_SECTION  g_cs;

    DWORD WINAP Thread1Func(…)

    {

       EnterCriticalSection(&g_cs);

        

       //发送消息,因为不知SendMessage要花费

       //多少时间,所以锁被占用的时间不能预估

       SendMessage(hwnd,WM_MYSTRUC,&g_mySt,0);

       

       LeaveCriticalSection(&g_cs);

       return 0

    }

    MYSTRUCT g_mySt;

    CRITICAL_SECTION  g_cs;

    DWORD WINAP Thread1Func(…)

    {

         EnterCriticalSection(&g_cs);

         //锁快照的时间很短(当然这个技巧是假设窗

         //口过程只需读取快照就足够了

         MYSTRUCT sTemp = g_mySt;

         LeaveCriticalSection(&g_cs);

        //将耗时的发送消息移到Enter/Leave之外

        SendMessage(hwnd,WM_MYSTRUC,& sTemp,0);

        return 0

    }

    分析:线程1中因SendMessage被包围在Enter/Leave之间,导致锁的时间不确定,可能很长,也可能很短。而线程2将耗时的SendMessage移到Enter/Leave之外,只锁定快照,这个CPU时间是可以预估的,只需几个CPU周期便可以。

    【ConditionSRWLock程序】条件变量的使用——读线程只能等到条件变量被设置时才能执行

    #include <windows.h>
    #include <tchar.h>
    #include <time.h>
    #include <locale.h>
    
    //////////////////////////////////////////////////////////////////////////
    const int g_iThreadCnt = 20;
    int g_iGlobalVal = 0;
    
    SRWLOCK            g_srwLock = {0};
    CONDITION_VARIABLE g_cv = { 0 };
    
    DWORD WINAPI ReadThread(PVOID pvParam);
    DWORD WINAPI WriteThread(PVOID pvParam);
    
    int _tmain()
    {
        _tsetlocale(LC_ALL, _T("chs"));
        srand((unsigned int)time(NULL));
    
        //初始化SRWLock锁和条件变量,不需要释放,系统会自行处理
        InitializeSRWLock(&g_srwLock);
        InitializeConditionVariable(&g_cv);
    
        HANDLE aThread[g_iThreadCnt];
        DWORD  dwThreadID = 0;
        SYSTEM_INFO si = { 0 };
        GetSystemInfo(&si);
    
        for (int i = 0; i < g_iThreadCnt; i++)
        {
            if (0== rand() % 2){
                aThread[i] = CreateThread(NULL, 0, 
                                          (LPTHREAD_START_ROUTINE)WriteThread,
                                          NULL,
                                          CREATE_SUSPENDED,&dwThreadID);
            } else{
                aThread[i] = CreateThread(NULL, 0,
                                          (LPTHREAD_START_ROUTINE)ReadThread,
                                          NULL,
                                          CREATE_SUSPENDED, &dwThreadID);
            }
    
            //设置线程的亲缘性
            SetThreadAffinityMask(aThread[i], 1 << (i%si.dwNumberOfProcessors));
            ResumeThread(aThread[i]);
        }
    
        //等待所有线程结束
        WaitForMultipleObjects(g_iThreadCnt, aThread, TRUE, INFINITE);
    
        for (int i = 0; i < g_iThreadCnt;i++){
            CloseHandle(aThread[i]);
        }
    
        _tsystem(_T("PAUSE"));
    
        return 0;
    }
    
    //读数据线程,以共享方式取得SRWLock锁
    DWORD WINAPI ReadThread(PVOID pvParam)
    {
        __try
        {
            //先获取共享锁
            AcquireSRWLockShared(&g_srwLock);
            //条件变量不满足时交出共享锁,并挂起自己以等待条件变量
            if (SleepConditionVariableSRW(&g_cv,&g_srwLock,20
                  ,CONDITION_VARIABLE_LOCKMODE_SHARED)){
                _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]读取全局变量值为%d
    "),
                         GetTickCount(),GetCurrentThreadId(),g_iGlobalVal);        
            } else{
                //超时的时候
                _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]读取全局变量值(未改变)为%d
    "),
                         GetTickCount(), GetCurrentThreadId(), g_iGlobalVal);
            }
        }
        __finally
        {
            ReleaseSRWLockShared(&g_srwLock); //释放锁,以便写线程有机会获得排它锁
        }
        return 0;
    }
    
    //写数据线程,以排他方式取得SRWLock锁
    DWORD WINAPI WriteThread(PVOID pvParam)
    {
        __try
        {
            AcquireSRWLockExclusive(&g_srwLock);
    
            for (int i = 0; i <= 4321;i++){
                g_iGlobalVal = i;
                SwitchToThread();  //为了演示,看下其他线程会不会读到“脏数据”
            }
            _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]写入数据值为%d
    "),
                     GetTickCount(), GetCurrentThreadId(), g_iGlobalVal);
        }
        __finally
        {
            ReleaseSRWLockExclusive(&g_srwLock); //释放锁,唤醒所有阻塞在Acuqire*函数中的线程
            WakeAllConditionVariable(&g_cv);     //唤醒所有阻塞在Sleep*中的读线程
        }
        return 0;
    }
  • 相关阅读:
    【BZOJ2879】【NOI2012】美食节(费用流)
    HN2018省队集训
    【HDU5421】Victor and String(回文树)
    【BZOJ2878】【NOI2012】迷失游乐园(动态规划)
    【BZOJ5338】[TJOI2018]异或(主席树)
    【BZOJ2432】【NOI2011】兔农(数论,矩阵快速幂)
    【BZOJ2436】【NOI2011】NOI嘉年华(动态规划)
    【BZOJ2437】【NOI2011】兔兔与蛋蛋(博弈论,二分图匹配)
    【BZOJ2109/2535】【NOI2010】航空管制(贪心)
    【BZOJ1565】【NOI2009】植物大战僵尸(网络流)
  • 原文地址:https://www.cnblogs.com/5iedu/p/4733248.html
Copyright © 2011-2022 走看看