吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2122|回复: 6
收起左侧

[系统底层] win32笔记十 事件

[复制链接]
huchen 发表于 2024-5-7 00:05
之前的笔记索引

事件

事件,他本身可以作为通知类型,什么是通知类型,代码里会介绍到

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
  BOOL bManualReset,                       // reset type
  BOOL bInitialState,                      // initial state
  LPCTSTR lpName                           // object name
);

这就是创建事件的函数

参数介绍

lpEventAttributes

安全描述符

bManualReset

这个有两个选项

  • 如果选择通知类型,就选TRUE,否则就填FALSE

bInitialState

创建出来是否有信号,即WaitForSingleObject或WaitForMultipleObject函数去立马得到他,TRUE有信号,FALSE没信号

lpName

这个Event起名字,要想在其他进程也得到这个Event就要写上名字,这里就只在当前进程里用,就不用起码,起名字了。

代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<windows.h>
HANDLE hEvent;
DWORD WINAPI ThreadProc1(LPVOID Lparmeter)
{
    TCHAR buff[10] = { 0 };

    //当事件变成已通知时
    WaitForSingleObject(hEvent, INFINITE);

    //线程执行
    printf("ThreadProc1执行了\n");
    getchar();
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID Lparmeter)
{
    TCHAR buff[10] = { 0 };

    //当事件变成已通知时
    WaitForSingleObject(hEvent, INFINITE);

    //线程执行
    printf("ThreadProc2执行了\n");
    getchar();
    return 0;
}

int main()
{
    //创建事件
    //默认安全属性  TRUE通知/FALSE互斥  初始没信号 无名字
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    //创建两个线程
    HANDLE hThread[2];
    hThread[0] = CreateThread(NULL, 0, ThreadProc1, 0, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc2, 0, 0, NULL);

    //设置事件为已通知
    //SetEvent(hEvent);

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

}

运行结果

image-20240422185001339.png

他会一直阻塞起,因为没有信号,我写的是FALSE,如果设置事件为已通知,我去掉注释,然后就能运行

image-20240422185150321.png

SetEvent就可以理解成改成有信号就行了

如果我把第二个参数改成FALSE,就相当于是一个互斥体

运行结果

image-20240422185520273.png

就会阻塞其中一个

这里TRUE就是通知类型,可以看到每个线程都有个WaitForSingleObject函数,当他看到是一个通知类型,那这个信号就不会变成0(1为有信号),所以另一个线程也能运行,没有阻塞

当变成FALSE时,可以理解成变成了互斥体,WaitForSingleObject函数就会改变这个信号,变成0,为无信号,那另一个线程在访问的时候,发现是为无信号就只能等,知道第一个线程重置信号为止,或者等待时间超时,返回

这就是event这个对象与互斥体第一个重要的区别

线程同步

  1. 线程互斥:线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排他性。当有若干个线程都要使用某同一共享资源时,任何时刻最多允许一个线程去使用,其他要使用该资源的线程必须等待,直到占用资源者释放该资源
  2. 线程同步:线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当他没有得到另一个线程的消息时应等待直到消息到达时才被唤醒

互斥只是保证了唯一性,就是任何时刻只能有一个线程去使用这个资源,不一定有序,比如,AB两线程共同使用C资源,假如A使用了后释放了,不一定B就使用了,可能还是A在使用,所以是无序 的

而同步是建立在互斥的基础上的,只是多了个有序,A使用后就轮到B了,然后又轮到A了,这样和平的画面,这就是同步

即  同步 = 互斥 + 有序

要实现同步的实验,莫过于生产与消费的代码了,看能不能用互斥来解决

代码

#include<stdio.h>
#include<windows.h>
HANDLE hMutex;      //创建一个互斥体
int hMax = 10;      //总共生产多少产品
int hNumber = 0;    //当前产品状态
DWORD WINAPI ThreadProc1(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hMutex, INFINITE);
        hNumber = 1;
        DWORD id = GetCurrentThreadId();//获取当前线程id
        printf("生产者%d将数据%d放入缓冲区\n",id,hNumber);
        ReleaseMutex(hMutex);
    }
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hMutex, INFINITE);
        hNumber = 0;
        DWORD id = GetCurrentThreadId();//获取当前线程id
        printf("------消费者%d将数据%d放入缓冲区\n", id, hNumber);
        ReleaseMutex(hMutex);
    }
    return 0;
}

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

    HANDLE hThread[2];

    hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);

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

    //销毁
    CloseHandle(hMutex);

    getchar();
    return 0;
}

我把可能会产生疑问的写了注释,其他的我就不讲了,都是讲过的

image-20240422194545431.png

可以看到这里的输出并不是按照我们想要的顺序输出的,但是两个线程都是遵守互斥的特性的,都只执行了十次(如果多运行几次,还会不同)

所以只用互斥的话,不能实现同步的效果,需要加东西

#include<stdio.h>
#include<windows.h>
HANDLE hMutex;
int hMax = 10;      //总共生产多少产品
int hNumber = 0;    //当前产品状态
DWORD WINAPI ThreadProc1(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hMutex, INFINITE);
        if (hNumber == 0)
        {
            hNumber = 1;
            DWORD id = GetCurrentThreadId();//获取当前线程id
            printf("生产者%d将数据%d放入缓冲区\n",id,hNumber);
        }
        else
        {
            i--;        //如果不是0,那么走else就会浪费一次机会,为了确保能输出十次,应该i--
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hMutex, INFINITE);
        if (hNumber == 1)
        {
            hNumber = 0;
            DWORD id = GetCurrentThreadId();//获取当前线程id
            printf("------消费者%d将数据%d放入缓冲区\n", id, hNumber);
        }
        else
        {
            i--;        //如果不是1,那么走else就会浪费一次机会,为了确保能输出十次,应该i--
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

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

    HANDLE hThread[2];

    hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);

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

    //销毁
    CloseHandle(hMutex);

    getchar();
    return 0;
}

image-20240422201827123.png

可以看到这里确实同步了,但是这只是从效果来说,不然的话event就没用了,这个代码是有硬伤的

看代码,

DWORD WINAPI ThreadProc1(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hMutex, INFINITE);
        if (hNumber == 0)
        {
            hNumber = 1;
            DWORD id = GetCurrentThreadId();//获取当前线程id
            printf("生产者%d将数据%d放入缓冲区\n",id,hNumber);
        }
        else
        {
            i--;        //如果不是0,那么走else就会浪费一次机会,为了确保能输出十次,应该i--
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

假设,这个线程获得了十毫秒的CPU时间,判断是否为0,不为0,那么走else,执行i--,消耗掉1毫秒,还剩下9毫秒,那剩下的时间这个代码是让出去还是浪费掉呢?

这个代码是没有让的概念,所以只能浪费掉,重复执行,所以会回来重新判断,不为1,继续else,直到剩下的9毫秒消耗掉

所以这个代码实现的同步,是在浪费CPU时间上进行的,验证

在else里加上一个输出吧,就不贴代码了

image-20240422203449726.png

image-20240422203528263.png
(这里我重新运行了好多次,都没有弄出视频中那中触目惊心的效果,就选了其中两个,有懂得大佬解释一下)

可以发现其中确实有浪费的现象,如果用事件得话能解决吗

代码

#include<stdio.h>
#include<windows.h>
HANDLE hSet;
HANDLE hClear;
int hMax = 10;      //总共生产多少产品
int hNumber = 0;    //当前产品状态

//生产者
DWORD WINAPI ThreadProc1(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hSet, INFINITE);
        hNumber = 1;
        DWORD id = GetCurrentThreadId();//获取当前线程id
        printf("生产者%d将数据%d放入缓冲区\n",id,hNumber);
        SetEvent(hClear);
    }
    return 0;
}

//消费者
DWORD WINAPI ThreadProc2(LPVOID Lparmeter)
{
    for (int i = 0; i < hMax; i++)
    {
        WaitForSingleObject(hClear, INFINITE);
        hNumber = 0;
        DWORD id = GetCurrentThreadId();//获取当前线程id
        printf("------消费者%d将数据%d放入缓冲区\n", id, hNumber);
        SetEvent(hSet);
    }
    return 0;
}

int main()
{
    hSet = CreateEvent(NULL, FALSE, TRUE, NULL);
    hClear = CreateEvent(NULL, FALSE, FALSE, NULL);

    HANDLE hThread[2];

    hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);

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

    //销毁
    CloseHandle(hSet);
    CloseHandle(hClear);

    getchar();
    return 0;
}

代码说明

hSet = CreateEvent(NULL, FALSE, TRUE, NULL);//生产
hClear = CreateEvent(NULL, FALSE, FALSE, NULL);//消费

这里设置了两个事件,一个生产,一个消费,这两个都是互斥关系,且生产初始为有信号,消费初始为无信号

然年执行到生产得线程里的

WaitForSingleObject(hSet, INFINITE);

由于是互斥的特性,所以这里会改变他的信号为0

然后执行到生产线程的

SetEvent(hClear);

这个上面说过是变成有信号是吧,看看具体的解释

如果事件是手动,该事件将保持信号,直到 ResetEvent  调用。 多可以在释放一个线程。 如果事件是自动的,则将保持信号,直到释放一个线程。 系统将设置操作的状态为nonsignaled。 如果没有等待线程,状态保持处于有信号状态,直到发布一个线程。

可以理解成消费信号开启了,配合WaitForSingleObject(hSet, INFINITE);形成了挂起自己,通知别人的现象,也就没有浪费的现象发生

消费的线程流程跟这个一样,就不多分析了

结果

image-20240422211018411.png

这才是真正意义上的同步

总结

  1. 介绍了新的API
  2. 用不同的方式来验证了线程同步,以及优缺点

免费评分

参与人数 2吾爱币 +8 热心值 +2 收起 理由
allspark + 1 + 1 用心讨论,共获提升!
willJ + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

woshicn 发表于 2024-5-7 08:21
感谢分享
Liyifeng1 发表于 2024-5-7 10:06
zjh889 发表于 2024-5-8 00:25
nitian0963 发表于 2024-5-8 09:05

谢谢大师分享!
j99d99 发表于 2024-5-8 13:51
感谢分享
xuiaipojie 发表于 2024-5-8 14:54
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-12 08:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表