2021-03-17Win3200
请注意,本文编写于 622 天前,最后修改于 204 天前,其中某些信息可能已经过时。

目录


1. 通知类型

c
HANDLE CreateEvent(
  	LPSECURITY_ATTRIBUTES lpEventAttributes, 	    //安全描述符
  	BOOL bManualReset,                       		//设置信号复位方式为自动恢复为无信号状态(FALSE)还是手动恢复为无信号状态(TRUE)
  	BOOL bInitialState,                      		//初始状态,创建出来时,是不是有信号
  	LPCTSTR lpName                           		//信号名称,可以为Null
);
c

DWORD WINAPI ThreadProc_1(LPVOID lpParameter)
{
    TCHAR szBuf[10] = {0};
    //当时间变成已通知
    WaitForSingleObject(g_hEvent,INFINITE);
    //线程执行
    printf("ThreadProc_1执行了\r\n");
    getchar();
    return 0;
}
DWORD WINAPI ThreadProc_2(LPVOID lpParameter)
{
    TCHAR szBuf[10] = {0};
    //当时间变成已通知
    WaitForSingleObject(g_hEvent,INFINITE);
    //线程执行
    printf("ThreadProc_2执行了\r\n");
    getchar();
    return 0;
}

int main()
{
    //创建事件
    //默认安全属性,TRUE通知/FALSE互斥,初始没信号,没名字
    //通知类型时,两个线程都会执行
    //互斥类型时,必须一个执行完,才能执行另一个
    //通知类型时,WaitForSingleObject不修改状态为占用
    //互斥类型时,WaitForSingleObject修改状态为占用
    g_hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    HANDLE hThread[2];
    //创建两个进程
    hThread[0] = CreateThread(NULL,0,ThreadProc_1,NULL,0,NULL);
    hThread[1] = CreateThread(NULL,0,ThreadProc_2,NULL,0,NULL);

    //设置事件为已通知,修改Event状态
    SetEvent(g_hEvent);

    //等待线程结束,销毁内核对象
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    CloseHandle(g_hEvent);
    return 0;
}

2. 线程同步

<1> 线程互斥:线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排
它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去
使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。

<2> 线程同步: 线程同步是指线程之间所具有的一种制约关系,一个线程的执行依
赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时
才被唤醒。

同步 = 互斥 + 有序

3. 同步的前提是互斥

同步的前提是互斥

互斥的问题就是,执行完进程A,接着执行的不一定是进程B,有可能依然是进程A

有序的意思就是,进程A和进程B,一边执行一次,轮流执行

Event事件和互斥体的区别

互斥体

c
// 生产.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

# include <windows.h>
# include <stdio.h>

HANDLE hMutex;
int g_Max = 10;        //生产几个产品
int g_Number = 0;     //容器 存储产品,可以看做是临界资源,一定是互斥的
//生产者线程函数
DWORD WINAPI ThreadProduct(LPVOID pM)
{
    for (int i = 0; i < g_Max; i++)
    {
        //互斥的访问缓冲区
        WaitForSingleObject(hMutex, INFINITE);
        if (g_Number == 0)
        {
            g_Number = 1;    //将全局变量改成1
            DWORD id = GetCurrentThreadId();    //获取当前执行的线程ID
            printf("生产者%d将数据%d放入缓冲区\r\n", id, g_Number);
        }
        else
        {
            //如果不符合if条件,那就返回i的次数,不能浪费本次循环
            i--;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}
//消费者线程
DWORD WINAPI ThreadConsumer(LPVOID pM)
{
    for (int i = 0; i < g_Max; i++)
    {
        //互斥的访问缓冲区
        WaitForSingleObject(hMutex, INFINITE);
        if (g_Number == 1)
        {
            g_Number = 0;    //将全局变量改回0
            DWORD id = GetCurrentThreadId();
            printf("-----消费者%d将数据%d放入缓冲区\r\n", id, g_Number);
        }
        else
        {
            //如果不符合if条件,那就返回i的次数,不能浪费本次循环
            i--;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

int main()
{
    //创建一个互斥体
    hMutex = CreateMutex(NULL, FALSE, NULL);
    HANDLE hThread[2];

    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);
    hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);

    //销毁
    CloseHandle(hMutex);

    return 0;
}

以上代码表面上看起来没问题,但是会有一定的浪费,因为,进入for循环之后,进入else,会浪费for多余的次数

Event事件

c
// 生产.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

# include <windows.h>
# include <stdio.h>

HANDLE g_hSet,g_hClear;
int g_Max = 10;        //生产几个产品
int g_Number = 0;     //容器 存储产品,可以看做是临界资源,一定是互斥的


//由于g_hSet初始值有信号的,所以WaitForSingleObject能够得到信号
//所以g_Number = 1;
//开始生产
//生产结束后,调用SetEvent来修改g_hClear变成有信号的
//由于当前的 g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL); g_hSet对象是FALSE是互斥的,所以一执行WaitForSingleObject就将g_hSet状态给修改成0了,所以此时即使再次执行生产者线程也无法执行,就会开始执行消费者线程

//生产者线程函数
DWORD WINAPI ThreadProduct(LPVOID pM)
{
    for (int i = 0; i < g_Max; i++)
    {
        WaitForSingleObject(g_hSet, INFINITE);
        g_Number = 1;    //将全局变量改成1
        DWORD id = GetCurrentThreadId();    //获取当前执行的线程ID
        printf("生产者%d将数据%d放入缓冲区\r\n", id, g_Number);
        SetEvent(g_hClear);    //一旦执行SetEvent,就把自己线程挂起了,不会再执行剩余次数,通知别人可以执行了
    }
    return 0;
}



//由于生产者线程执行完之后将g_hClear状态改为了有信号,所以WaitForSingleObject能够得到信号
//所以g_Number = 0;
//开始消费
//消费结束后,调用SetEvent来修改g_hSet变成有信号的
//由于当前的 g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL); g_hClear对象是FALSE是互斥的,所以一执行WaitForSingleObject就将g_hClear状态给修改成0了,所以此时即使再次执行消费者线程也无法执行,就会开始执行生产者线程
//消费者线程
DWORD WINAPI ThreadConsumer(LPVOID pM)
{
    for (int i = 0; i < g_Max; i++)
    {
        WaitForSingleObject(g_hClear, INFINITE);
        g_Number = 0;    //将全局变量改成0
        DWORD id = GetCurrentThreadId();    //获取当前执行的线程ID
        printf("-----消费者%d将数据%d放入缓冲区\r\n", id, g_Number);
        SetEvent(g_hSet);    //一旦执行SetEvent,就把自己线程挂起了,不会再执行剩余次数,通知别人可以执行了
    }
    return 0;
}

int main()
{
    HANDLE hThread[2];

    //安全描述符,互斥,有信号的,名字(不写),给生产者线程用的
    g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL);
    //安全描述符,互斥,无信号的,名字(不写),给消费者线程用的
    g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL);

    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);
    hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);

    //销毁
    CloseHandle(g_hSet);
    CloseHandle(g_hClear);

    return 0;
}

区别

  1. Event事件有通知类型,可以同时通知很多其他的线程,也可以互斥
  2. 互斥体没有办法实现线程同步,但是Event可以实现线程同步

本文作者:Na1r

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!