zoukankan      html  css  js  c++  java
  • 第9章 用内核对象进行线程同步(1)_事件对象(Event)

    9.1 等待函数

    (1)WaitForSingleObject(hObject,dwMilliseonds);

      ①dwMilliseconds为INFINITE时表示无限等待

      ②dwMilliseconds=0时表示立即返回,即使它要等待的条件还没满足

      ③dwMilliseconds为其它值时(单位为ms),其返回值有三种情况:A、WAIT_OBJECT_0表示等待的对象触发。WAIT_TIMEOUT表示超时。WAIT_FAILED:表示可能传入了无效句柄,可进一步调用GetLastError来得到更详细的信息。

    (2)WaitForMultipleObjects函数

    参数

    描述

    nCount

    要等待的内核对象的数量,这个值必须在1~MAXIMUM_WAIT_OBJECTS之间(即最多64个)

    lpHandles

    指向等待的内核对象的数组

    bWaitAll

    TRUE时表示等待的所有内核对象都触发FALSE:只要有一个触发就返回

    dwMilliseconds

    与WaitForSingleObject的含义相同

    返回值

    WAIT_FAILED:传入了无效句柄。

    WAIT_TIMEOUT:超时

    WAIT_OBJECT_0~WAIT_OBJECT_x:如果bWaitAll为TRUE时,则当所有对象被触发时返回WAIT_OBJECT_0;如果bWaitAll为FALSE,则只要有一个对象被触发,函数就返回WAIT_OBJECT_x,其中的x表示上述被触发对象在lpHandles数组中的下标,即用于通知是哪个对象被触发。

    9.2 等待成功所引起的副作用

    (1)WaitForSingleObject或WaitForMultipleObjects函数在调用成功后,如果对象的状态也被改变了,这称为“等待成功所引起的副作用”(注意调用失败并不会改变对象状态)。如事件对象(Event),当为“自动设置”时,在调用WaitForSingleObject成功以后会被自动恢复初始的为“无信号”状态,这就是副作用。当然如果设置为“手动”,则不会有这种副作用。此外,其他对象有的也有副作用,但有的完全没有。

    (2)举例说明等待成功的副作用(设两个线程以完全相同的方式调用WaitForMultiple*函数)

        HANDLE hEvent[2];

        hEvent[0] = hAutoResetEvent1; //初始化时为“无信号”状态

        hEvent[1] = hAutoResetEvent2; //初始化时为“无信号”状态

        WaitForMultipleObjects(2,hEvent,TRUE,INFINITE);

      ①刚调用WaitFor*函数时,两个事件对象均未被触发,所以两个线程都进入等待状态。

      ②然后当hAutoResetEvent1被触发,但由于hAutoResetEvent2未被触发,所以系统不会唤醒任何一个线程,所以WaitFor*函数没有成功返回,不会对hAutoResetEvent1对象产生副作用。

      ③接下来hAutoResetEvent2被触发,此时两个线程中的一个检测到,并等待成功。这时该线程的WaitFor*函数会以原子方式将两个事件对象重设为“无信号”状态(即在改变两个事件状态的过程不允许被打断,此时其它线程无法访问和改变这两个事件对象,这种原子方式的特征是为了防止死锁的发生,见课本P236页)。但对于另一个线程来说,虽然他曾经检测到hAutoResetEvent1被触发,但现在看到的状态却是未触发,所以它会继续等待,直到这两个对象都被触发。这就是副作用,因为hAutoResetEvent曾经被触发过,而现在还要重新等待。

    9.3 事件内核对象

    (1)创建事件内核对象:CreateEvent函数

    参数

    描述

    psa

    安全属性(如使用计数、句柄继承等)

    bManualReset

    TRUE:手动重置事件,两个特点:①当事件被触发时,正在等待该事件的所有线程都变为可调度;②调用WaitFor*函数后,不会自动重置对象为“无信号”状态

    FALSE:自动重置事件,两个特点:①当事件被触发时,只有一个正在等待该事件的线程变为可调度;②调用WaitFor*函数会,会自动将事件的状态重置为“无信号”

    bInitialState

    创建时的初始状态,TRUE为有信号,FALSE为无信号。

    pszName

    对象的名字

    返回值

    返回与当前进程相关的事件内核对象句柄

    (2)CreateEventEx函数

    参数

    描述

    psa

    安全属性(如使用计数、句柄继承等)

    pszName

    对象的名字

    dwFlags

    CREATE_EVENT_INITIAL_SET(0x02):初始化为触发状态

    CREATE_EVENT_MANUAL_RESET(0x01):手动重置事件

    dwDesiredAccess

    返回句柄的访问权限

    返回值

    返回与当前进程相关的事件内核对象句柄

    (3)SetEvent——将事件设为触发状态

    (4)ResetEvent——重置事件为未触发状态

        ★对于自动重置事件:当线程成功等到事件对象时,会自动重置为未触发状态,而不必调用ResetEvent函数

    (5)PulseEvent函数——脉冲事件函数

      ①先触发事件然后立刻恢复到未触发状态,相当于调用SetEvent后立即调用ResetEvent。

      ②如果是手动重置事件调用PulseEvent,则会将所有正在等待事件的线程变成可调度状态。如果是自动重置事件,那么只有一个正在等待该事件的线程会变成可调度状态。

      ③如果当事件被脉冲触发的时候没有线程正在等待该事件,则不会产生任何效果

    (6)手动重置事件与自动重置事件的比较

    【伪代码分析】

    #include <stdio.h>
    #include <windows.h>
    #include <process.h>
    #include <tchar.h>
    
    typedef unsigned(__stdcall *PTHREAD_START) (void *);
    
    DWORD WINAPI WordCount(PVOID pvParam);
    DWORD WINAPI SpellCheck(PVOID pvParam);
    DWORD WINAPI GrammarCheck(PVOID pvParam);
    
    HANDLE g_hEvent;
    
    int _tmain()
    {
        //创建一个手动,未触发的事件
        g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    
        //创建3个线程
        HANDLE hThread[3];
        DWORD  dwThreadID;
        hThread[0] = (HANDLE)_beginthreadex(NULL, 0, (PTHREAD_START)WordCount,NULL, 0,(unsigned *)&dwThreadID);
        hThread[1] = (HANDLE)_beginthreadex(NULL, 0, (PTHREAD_START)SpellCheck,NULL, 0, (unsigned *)&dwThreadID);
        hThread[2] = (HANDLE)_beginthreadex(NULL, 0, (PTHREAD_START)GrammarCheck,NULL, 0, (unsigned *)&dwThreadID);
    
        //打开文件并读入内存
        OpenFileAndReadContentsIntoMemory(...);
    
        //因为是手动重置事件,所以会同时唤醒3个线程
        //假设上面的Open*函数己经把数据读到内存中,则当3个线程被唤醒
        //时则会时候对该文件进行“字数统计”、“拼写检查”和“语法检查”
        //因为这3个操作都是读操作,所以可以同时进程。
        SetEvent(g_hEvent);
        ...
        return 0;
    }
    
    //“字数统计”
    DWORD WINAPI  WordCount(PVOID pvParam)
    {
        //等待文件的数据被全部载入内存
        WaitForSingleObject(g_hEvent, INFINITE);
    
        //访问内存块,因3个线程同时访问内存,所以这里只能是共享方式访问
        ...
        return 0;
    }
    
    //“拼写检查”
    DWORD WINAPI  SpellCheck(PVOID pvParam)
    {
        //等待文件的数据被全部载入内存
        WaitForSingleObject(g_hEvent, INFINITE);
    
        //访问内存块,因3个线程同时访问内存,所以这里只能是共享方式访问
        ...
            return 0;
    }
    
    //“语法检查”
    DWORD WINAPI  GrammarCheck(PVOID pvParam)
    {
        //等待文件的数据被全部载入内存
        WaitForSingleObject(g_hEvent, INFINITE);
    
        //访问内存块,因3个线程同时访问内存,所以这里只能是共享方式
        ...
            return 0;
    }
    
    //如果将以上的事件对象改为自动重置时,即
    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    
    //此时的只要唤醒其中一个线程
    SetEvent(g_hEvent);
    
    //此时的各线程函数会改为如果的形式,这时只有一个线程被唤醒
    DWORD WINAPI  WordCountSpellCheckGrammarCheck (PVOID pvParam)
    {
        //等待文件的数据被全部载入内存
        WaitForSingleObject(g_hEvent, INFINITE); //因为是自动对象,该函数调用后会自动被设为未触发
    
        //访问内存块,因为此时只唤醒一个线程,所以对内存的访问可以是可读可写的
        ...
        SetEvent(g_hEvent); //唤醒剩下的2个线程中的一个,这句是与手动事件不同的,因为手动会全部唤醒,无需这句
        return 0;
    }

     【HandShake示例程序】——演示事件内核对象的使用,用于通知某项任务是否完成

    /************************************************************************
    Module:Handshake.cpp
    Notices:Copyright(c)2008 Jeffrey Richter & Christophe Nasarre
    ************************************************************************/
    #include "../../CommonFiles/CmnHdr.h"
    #include <tchar.h>
    #include "resource.h"
    
    //////////////////////////////////////////////////////////////////////////
    //当客户端发出一个请求后,事件触发
    HANDLE g_hevtRequestSubmitted;
    
    //当服务端发出一个结果给客户端时,事件触发
    HANDLE g_hevtResultReturned;
    
    //客户线程与服务线程共享的缓冲区
    TCHAR g_szSharedRequestAndResultBuffer[1024];
    
    //客户端发送一个特殊的字符串,用来结束程序
    TCHAR g_szServerShutdown[] = TEXT("Server Shutdown");
    
    //主对话框句柄。当服务线程接收到关闭消息时,会检测该句柄是否有效
    HWND g_hMainDlg;
    
    //////////////////////////////////////////////////////////////////////////
    DWORD WINAPI ServerThread(PVOID pvParam)
    {
        //假设服务线程没被停止
        BOOL fShutdown = FALSE;
    
        while (!fShutdown){
    
            //等待客户线程提交请求
            WaitForSingleObject(g_hevtRequestSubmitted, INFINITE);
    
            //检查是否要结束程序(并关闭对话框时,会设置退出字符串)
            //这里检查g_hMainDlg==NULL,可以防止用户手动输入“Server Shutdown”字符串
            //而导致该线程退出的现象,也就是用户输入该字符串并不退出。
            fShutdown = (g_hMainDlg == NULL) &&
                (_tcscmp(g_szSharedRequestAndResultBuffer, g_szServerShutdown) == 0);
    
            if (!fShutdown){
                //处理客户线程的请求
                _tcsrev(g_szSharedRequestAndResultBuffer);  //反转字符串
            }
    
            //通知客户线程,服务线程己处理完毕
            SetEvent(g_hevtResultReturned);
        }
    
        //客户线程要求退出
        return 0;
    }
    
    //////////////////////////////////////////////////////////////////////////
    BOOL  Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
    {
        chSETDLGICONS(hwnd, IDI_HANDSHAKE);
    
        //初始化编辑框
        Edit_SetText(GetDlgItem(hwnd, IDC_REQUEST), TEXT("Some test data"));
    
        g_hMainDlg = hwnd;
        return TRUE;
    }
    
    void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotify)
    {
        switch (id)
        {
        case IDCANCEL:
            EndDialog(hwnd, id);
            break;
        case IDC_SUBMIT:
    
            //将客户请求的字符串拷入共享缓冲区
            Edit_GetText(GetDlgItem(hwnd, IDC_REQUEST),
                         g_szSharedRequestAndResultBuffer,_countof(g_szSharedRequestAndResultBuffer));
            //客户线程提交请求,表示缓冲区己准备好。并等待服务线程返回处理结果
            SignalObjectAndWait(g_hevtRequestSubmitted,
                                g_hevtResultReturned, INFINITE,FALSE);
            
            //显示服务线程的处理结果
            Edit_SetText(GetDlgItem(hwnd, IDC_RESULT), g_szSharedRequestAndResultBuffer);
            break;
        }
    }
    
    INT_PTR WINAPI Dlg_Proc(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)
    {
        //创建并初始化2个未触发的自动事件对象
        g_hevtRequestSubmitted = CreateEvent(NULL, FALSE, FALSE, NULL);
        g_hevtResultReturned = CreateEvent(NULL, FALSE, FALSE, NULL);
    
        //创建服务线程
        DWORD dwThreadID;
        HANDLE hThreadServer = chBEGINTHREADEX(NULL, 0, ServerThread, NULL, 0, &dwThreadID);
    
        //运行UI线程(主线程,也是客户线程)
        DialogBox(hInstExe, MAKEINTRESOURCE(IDD_HANDSHAKE), NULL, Dlg_Proc);
        g_hMainDlg = NULL; //表示对话框己退出
    
        //主线程UI线程正在关闭,这时服务线程可能被阻塞在Wait*函数中,将其唤醒,
        //并填写退出字符串,以便退出
        _tcscpy_s(g_szSharedRequestAndResultBuffer,
                 _countof(g_szSharedRequestAndResultBuffer),g_szServerShutdown);
        SetEvent(g_hevtRequestSubmitted);
    
        //等待服务线程退出
        HANDLE h[2];
        h[0] = g_hevtResultReturned;
        h[1] = hThreadServer;
        WaitForMultipleObjects(2, h, TRUE, INFINITE);
    
        //关闭所有的句柄
        CloseHandle(g_hevtResultReturned);
        CloseHandle(g_hevtRequestSubmitted);
        CloseHandle(hThreadServer);
        return 0;
    }

    //resource.h

    //{{NO_DEPENDENCIES}}
    // Microsoft Visual C++ 生成的包含文件。
    // 供 9_HandShake.rc 使用
    //
    #define IDD_HANDSHAKE                   101
    #define IDI_HANDSHAKE                   102
    #define IDC_REQUEST                     1002
    #define IDC_RESULT                      1003
    #define IDC_SUBMIT                      1004
    
    // 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         1003
    #define _APS_NEXT_SYMED_VALUE           101
    #endif
    #endif

    //Handshake.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_HANDSHAKE DIALOGEX 0, 0, 237, 91
    STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU
    CAPTION "Handshake"
    FONT 8, "MS Shell Dlg", 400, 0, 0x1
    BEGIN
        DEFPUSHBUTTON   "提交到服务端",IDC_SUBMIT,89,41,65,14
        GROUPBOX        "客户端",IDC_STATIC,8,7,223,78
        LTEXT           "请求:",IDC_STATIC,16,23,25,8
        LTEXT           "结果:",IDC_STATIC,15,65,25,8
        EDITTEXT        IDC_REQUEST,44,21,179,14,ES_AUTOHSCROLL
        EDITTEXT        IDC_RESULT,43,62,179,14,ES_AUTOHSCROLL | ES_READONLY
    END
    
    
    /////////////////////////////////////////////////////////////////////////////
    //
    // DESIGNINFO
    //
    
    #ifdef APSTUDIO_INVOKED
    GUIDELINES DESIGNINFO
    BEGIN
        IDD_HANDSHAKE, DIALOG
        BEGIN
        END
    END
    #endif    // APSTUDIO_INVOKED
    
    
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDI_HANDSHAKE           ICON                    "Handshake.ico"
    #endif    // 中文(简体,中国) resources
    /////////////////////////////////////////////////////////////////////////////
    
    
    
    #ifndef APSTUDIO_INVOKED
    /////////////////////////////////////////////////////////////////////////////
    //
    // Generated from the TEXTINCLUDE 3 resource.
    //
    
    
    /////////////////////////////////////////////////////////////////////////////
    #endif    // not APSTUDIO_INVOKED

     【Event程序】模拟共享区的读取

    #include <windows.h>
    #include <tchar.h>
    #include <locale.h>
    
    //////////////////////////////////////////////////////////////////////////
    #define THREADCNT 6
    HANDLE g_hWriteEvent = NULL;
    HANDLE g_hThreads[THREADCNT] = { NULL };
    
    //////////////////////////////////////////////////////////////////////////
    DWORD  WINAPI ThreadProc(PVOID pvParam);
    void  CreateEventsAndThreads(void);
    void WriteToBuffer(void);
    void CloseEvents();
    
    int _tmain()
    {
        _tsetlocale(LC_ALL, _T("chs"));
        CreateEventsAndThreads();
        WriteToBuffer(); //将数据写入缓冲区。写完后,通过SetEvent通知各线程去读取
        _tprintf(_T("
    "));
    
        DWORD dwWaitResult;
        dwWaitResult = WaitForMultipleObjects(THREADCNT, g_hThreads, TRUE, INFINITE);
        switch (dwWaitResult)
        {
        case WAIT_OBJECT_0: //因为bWaitAll为TRUE,所以当全部对象被触发,会返回Wait_object_0;
            _tprintf(_T("
    子线程全部结束!
    "));
            break;
        default:
            _tprintf(_T("
    WaitForMulipleObjects错误(%d)
    "), GetLastError());
            break;
    
        }
    
        CloseEvents();
        _tsystem(_T("PAUSE"));
        return 0;
    }
    
    DWORD  WINAPI ThreadProc(PVOID pvParam)
    {
        DWORD dwWaitResult = 0;
        _tprintf(_T("线程[%d]正在等待“写事件”被触发
    "),GetCurrentThreadId());
        dwWaitResult = WaitForSingleObject(g_hWriteEvent, INFINITE);
    
        switch (dwWaitResult)
        {
        case WAIT_OBJECT_0:
            _tprintf(_T("线程[%d]正在读取缓冲区中的数据...
    "), GetCurrentThreadId());
            break;
    
        default:
            _tprintf(_T("等待错误(%d)
    "), GetLastError());
            break;
        }
        _tprintf(_T("线程[%d]退出!
    "), GetCurrentThreadId());
        return 0;
    }
    
    void  CreateEventsAndThreads(void)
    {
        //创建手动重置事件,这可以使所有线程都被唤醒。
        //如果改为自动,那么每次只能唤醒一个线程,程序会出现问题
        //这就是成功等待的副作用
        g_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, _T("WriteEvent"));
    
        for (int i = 0; i < THREADCNT;i++){
            g_hThreads[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        }
    }
    
    void WriteToBuffer(void)
    {
        _tprintf(_T("主线程正在写入数据到共享区...
    "));
    
        if (!SetEvent(g_hWriteEvent)){
            _tprintf(_T("SetEvent失败(%d)
    "),GetLastError());
        }
    }
    
    void CloseEvents()
    {
        CloseHandle(g_hWriteEvent);
    }
  • 相关阅读:
    CentOS 6.6 系统升级到 CentOS 6.7
    Nginx 默认的日志类型
    windows 系统后台运行 jar 包
    windows 下启动运行 jar 包程序
    Zabbix 添加端口监控链接
    提取 linux 文件目录结构
    Android LayoutInflater详解
    String,StringBuffer与StringBuilder的区别??
    Android中Cursor类的概念和用法
    Intent中的四个重要属性——Action、Data、Category、Extras
  • 原文地址:https://www.cnblogs.com/5iedu/p/4738411.html
Copyright © 2011-2022 走看看