zoukankan      html  css  js  c++  java
  • 【多线程】学习5

    内容来自:http://blog.csdn.net/morewindows/article/details/7445233

    本篇介绍用事件Event来尝试解决这个线程同步问题。

    首先介绍下如何使用事件。事件Event实际上是个内核对象,它的使用非常方便。下面列出一些常用的函数。

     

    第一个 CreateEvent

    函数功能:创建事件

    函数原型:

    HANDLE CreateEvent(

     LPSECURITY_ATTRIBUTES lpEventAttributes,

     BOOL bManualReset,

     BOOL bInitialState,

     LPCTSTR lpName

    );

    函数说明:

    第一个参数表示安全控制,一般直接传入NULL。

    第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。

    第三个参数表示事件的初始状态,传入TRUR表示已触发。

    第四个参数表示事件的名称,传入NULL表示匿名事件。

     

    第二个 OpenEvent

    函数功能:根据名称获得一个事件句柄。

    函数原型:

    HANDLE OpenEvent(

     DWORD dwDesiredAccess,

     BOOL bInheritHandle,

     LPCTSTR lpName     //名称

    );

    函数说明:

    第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。

    第二个参数表示事件句柄继承性,一般传入TRUE即可。

    第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。

     

    第三个SetEvent

    函数功能:触发事件

    函数原型:BOOL SetEvent(HANDLE hEvent);

    函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。

     

    第四个ResetEvent

    函数功能:将事件设为末触发

    函数原型:BOOL ResetEvent(HANDLE hEvent);

     

    最后一个事件的清理与销毁

    由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

    在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:

    #include <stdio.h>
    #include <process.h>
    #include <Windows.h>
    
    long g_nNum;
    unsigned int __stdcall Fun(void * pPM);
    const int THREAD_NUM = 10;
    
    //事件与关键段
    HANDLE g_hThreadEvent;
    CRITICAL_SECTION g_csThreadCode;
    int main()
    {
        //初始化事件和关键段 自动置位,初始无触发的匿名事件
        g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        InitializeCriticalSection(&g_csThreadCode);
    
        HANDLE handle[THREAD_NUM];
        g_nNum = 0;
        int i = 0;
        while(i < THREAD_NUM)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
            WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件触发
            i++;
        }
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    
        //销毁事件和关键段
        CloseHandle(g_hThreadEvent);
        DeleteCriticalSection(&g_csThreadCode);
        return 0;
    }
    
    unsigned int __stdcall Fun(void * pPM)
    {
        int nThreadNum = *(int *)pPM;
        SetEvent(g_hThreadEvent); //触发事件
    
        Sleep(50);
    
        EnterCriticalSection(&g_csThreadCode);
        g_nNum++;
        Sleep(0);
        printf("线程编号为%d  全局资源值为%d
    ", nThreadNum, g_nNum);   
        LeaveCriticalSection(&g_csThreadCode);
        return 0;
    }

    可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。

    注:只要编号不重复就成功了,不需要编号递增,要不然就不是并行了

    现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了PulseEvent()函数,所以接下来再继续深挖下事件Event,看看它还有什么秘密没。

    先来看看这个函数的原形:

    第五个PulseEvent

    函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。

    函数原型:BOOL PulseEvent(HANDLE hEvent);

    函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:

    1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。

    2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。

    此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。

    最好不要用PulseEvent ()!

     

     下面对这个触发一个事件脉冲PulseEvent ()写一个例子,主线程启动7个子线程,其中有5个线程Sleep(10)后对一事件调用等待函数(称为快线程),另有2个线程Sleep(100)后也对该事件调用等待函数(称为慢线程)。主线程启动所有子线程后再Sleep(50)保证有5个快线程都正处于等待状态中。此时若主线程触发一个事件脉冲,那么对于手动置位事件,这5个线程都将顺利执行下去。对于自动置位事件,这5个线程中会有中一个顺利执行下去。而不论手动置位事件还是自动置位事件,那2个慢线程由于Sleep(100)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。

    代码如下:

    #include <stdio.h>
    #include <conio.h>
    #include <process.h>
    #include <Windows.h>
    
    HANDLE g_hThreadEvent;
    //快线程
    unsigned int __stdcall FastThreadFun(void *pPM)
    {
        Sleep(10);
        printf("%s 启动
    ", (PSTR)pPM);
        WaitForSingleObject(g_hThreadEvent, INFINITE);
        printf("%s 等到事件被触发 顺利结束
    ", (PSTR)pPM);
        return 0;
    }
    //慢线程
    unsigned int __stdcall SlowThreadFun(void *pPM)
    {
        Sleep(100);
        printf("%s 启动
    ", (PSTR)pPM);
        WaitForSingleObject(g_hThreadEvent, INFINITE);
        printf("%s 等到事件被触发 顺利结束
    ", (PSTR)pPM);
        return 0;
    }
    
    int main()
    {
        BOOL bManualReset = FALSE;
        g_hThreadEvent = CreateEvent(NULL, bManualReset, FALSE, NULL);
        if(bManualReset == TRUE)
        {
            printf("当前使用手动置位事件
    ");
        }
        else
        {
            printf("当前使用自动置位事件
    ");
        }
        char szFastThreadName[5][30] = {"快线程1000", "快线程1001", "快线程1002", "快线程1003", "快线程1004"};  
        char szSlowThreadName[2][30] = {"慢线程196", "慢线程197"};  
    
        int i;
        for(i = 0; i < 5; i++)
            _beginthreadex(NULL,0,FastThreadFun, szFastThreadName[i], 0, NULL);
        for (i = 0; i < 2; i++)  
            _beginthreadex(NULL, 0, SlowThreadFun, szSlowThreadName[i], 0, NULL); 
    
        Sleep(50); //保证快线程已经全部启动  
        printf("现在主线程触发一个事件脉冲 - PulseEvent()
    ");  
        PulseEvent(g_hThreadEvent);//调用PulseEvent()就相当于同时调用下面二句  
        //SetEvent(g_hThreadEvent);  
        //ResetEvent(g_hThreadEvent);  
          
        Sleep(3000);   
        printf("时间到,主线程结束运行
    ");  
        CloseHandle(g_hThreadEvent);  
        return 0;  
    }

    自动复位事件,运行结果如下:

    手动置位事件

    最后总结下事件Event

    1.事件是内核对象,事件分为手动置位事件自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。

    2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。

    3.事件可以解决线程间同步问题,因此也能解决互斥问题。

    ------------------------------------------------------------------------------

    更多思考:

    更新2015-7-21

    用事件实现互斥

     

    上面的使用中是用事件处理子线程与主线程的同步,用关键段处理子线程之间的互斥。这里试图用事件处理互斥,即g_nNum的递增。

    代码如下:

    #include <stdio.h>
    #include <process.h>
    #include <Windows.h>
    
    long g_nNum;
    unsigned int __stdcall Fun(void *pPM);
    const int THREAD_NUM = 30; //子线程个数
    HANDLE  g_hThreadEvent, g_hThreadCode; 
    
    int main()
    {
        g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        g_hThreadCode = CreateEvent(NULL, FALSE, TRUE, NULL); //一开始设为触发状态 保证第一个线程能够进人临界区
    
        g_nNum = 0;
        HANDLE handle[THREAD_NUM];
        int i = 0;
        while(i < THREAD_NUM)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
            WaitForSingleObject(g_hThreadEvent, INFINITE);
            Sleep(0); 
            i++;
        }
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    
        CloseHandle(g_hThreadEvent);  
        CloseHandle(g_hThreadCode);  
        return 0;
    }
    unsigned int __stdcall Fun(void *pPM)
    {
        int nThreadNum = *(int *)pPM;
        SetEvent(g_hThreadEvent); //这句放在这里就够了 保证每次都等参数传给了nThreadNum后 i才会变 
        Sleep(10);
    
        WaitForSingleObject(g_hThreadCode, INFINITE); //其他线程在这里开始等待
        g_nNum++;
        Sleep(0);
        printf("线程编号为%d 全局资源值为%d
    ", nThreadNum, g_nNum);
        SetEvent(g_hThreadCode); //出临界区后 触发事件使得其他线程可以进人临界区
        
        return 0;
    }

    关键部分的代码,我用粗体显示了。在子线程的Fun函数中,我们在g_nNum变化前加上了WaitForSingleObject()函数,这样,如果没有其他线程触发事件,就不能够进入下面的临界区。

    处理结束后,我们用SetEvent()触发事件,这样其他线程可以进入临界区。

    在主线程创建Event时,初始化为已触发,保证第一个运行到WaitForSingleObject的子线程能够运行。

    注:对于事件,一定是先写WaitForObject,再写SetEvent。因为需要等待某些事件的发生。

    如果反过来先写SetEvent,再写WaitForObject,则每次遇到WaitForObject时事件都是已经触发的,那Event就没有意义了!

     

     

     

    下面的内容是原来写的,如今重新看了一遍,发现完全不对。故删除!

    看到原博客下面一个评论,试图用Event解决互斥事件,他是这样写的

    用事件来解决互斥问题,以下的例子有问题么?lz和看见的各位麻烦回答一下
    
    
    #include "stdafx.h"
    #include <stdio.h>
    #include<windows.h>
    #include<process.h>
    
    const int THREAD_NUM = 10;//子线程数
    int g_nNum;//全局变量
    unsigned int __stdcall Fun(void *pSeq);
    HANDLE EventHandle;//事件句柄
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        HANDLE handle[THREAD_NUM];
        int i = 0;
        g_nNum = 0;
        EventHandle = CreateEvent(NULL,FALSE,FALSE,NULL);
    
        while(i<THREAD_NUM)
        {
            handle[i] =(HANDLE)_beginthreadex(NULL,0,Fun,NULL,0,NULL);
            Sleep(10);
            i++;
            SetEvent(EventHandle);
        }
        system("pause");
        return 0;
    }
    
    unsigned int __stdcall Fun(void *pSeq)
    {
        int gThreadCount = g_nNum;
        g_nNum ++;
        WaitForSingleObject(EventHandle,INFINITE);
        Sleep(50);//some work to do
        printf("全局变量[%d]
    ",gThreadCount);
        return 0;
    }

    这样写是有问题的,加两个输出语句看下

    while(i < THREAD_NUM)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
            WaitForSingleObject(g_hThreadEvent, INFINITE);
            Sleep(10); //这里 Sleep的时间长度必须大于 Fun里第一个Sleep的时间长度 否则SetEvent(g_hThreadCode);会运行很多遍,然后自动复位只能响应这些事件中的一个
            printf("%d
    ", i);
            i++;
            SetEvent(g_hThreadCode);
            printf("j
    ");
        }

      可以看出SetEvent(g_hThreadCode);可以连续运行很多次,即我们多次把g_hThreadCode设成触发状态,然后在进入一次Fun后

    g_hThreadCode被复位了,其他的Fun没有运行。 必须主函数的Sleep的时间比Fun中的Sleep时间长才可以,保证

    SetEvent和WaitForSingleObject函数一一对应。

    ------------------------

    ??疑问:如果几个Fun函数卡在WaitForSingleObject(EventHandle,INFINITE);那理论上应该一直等待啊?为什么返回了??

    ------------------------------------------

    修改后:

    #include <stdio.h>
    #include <process.h>
    #include <Windows.h>
    
    long g_nNum;
    unsigned int __stdcall Fun(void *pPM);
    const int THREAD_NUM = 10; //子线程个数
    
    HANDLE  g_hThreadEvent, g_hThreadCode; 
    
    int main()
    {
        g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        g_hThreadCode = CreateEvent(NULL, FALSE, FALSE, NULL);
    
        g_nNum = 0;
        HANDLE handle[THREAD_NUM];
        int i = 0;
        while(i < THREAD_NUM)
        {
            handle[i] = (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
            WaitForSingleObject(g_hThreadEvent, INFINITE);
            Sleep(20); //这里 Sleep的时间长度必须大于 Fun里第一个Sleep的时间长度 否则SetEvent(g_hThreadCode);会运行很多遍,然后自动复位只能响应这些事件中的一个
            //printf("%d
    ", i);
            i++;
            SetEvent(g_hThreadCode);
            //printf("j
    ");
        }
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    
        CloseHandle(g_hThreadEvent);  
        CloseHandle(g_hThreadCode);  
        return 0;
    }
    unsigned int __stdcall Fun(void *pPM)
    {
        int nThreadNum = *(int *)pPM;
        SetEvent(g_hThreadEvent); //这句放在这里就够了 保证每次都等参数传给了nThreadNum后 i才会变
        
        Sleep(10);
        g_nNum++;
        WaitForSingleObject(g_hThreadCode, INFINITE);
    
        Sleep(0);
        printf("线程编号为%d 全局资源值为%d
    ", nThreadNum, g_nNum);
        
        return 0;
    }

    结果图为:

    这时数字显示与预想的一样了,但实际上,这就已经失去了多线程的意义了,实际的运行就是顺序运行的。

    其实这样写事件根本就没有意义!去掉g_hThreadCode也会得到相同的结果。完全是靠sleep保证主线程与子线程的顺序的!!

     

  • 相关阅读:
    sublime text 4 vim 插件配置
    ssh-keygen 的使用
    distribution transaction solution
    bilibili 大数据 视频下载 you-get
    Deepin 20.2.1 安装 MS SQL 2019 容器版本
    【转】使用Linux下Docker部署MSSQL并加载主机目录下的数据库
    【转】You Can Now Use OneDrive in Linux Natively Thanks to Insync
    dotnet 诊断工具安装命令
    Linux 使用 xrandr 设置屏幕分辨率
    【转】CentOS 7.9 2009 ISO 官方原版镜像下载
  • 原文地址:https://www.cnblogs.com/dplearning/p/4028287.html
Copyright © 2011-2022 走看看