watermelon 发表于 2018-6-12 11:24:45

线程同步之事件对象

昨天晚上小弟看了一下WindowsAPI的书,然后学习到了线程同步,想在论坛搜素一下线程同步,看看大佬们的程序,发现没有帖子,那我就发一个贴,这个就是相当于一个学习笔记吧。
下面的这个例子改编自网上很多博客的售票的问题,只不过我改成了吃西瓜,并且自己在程序中加了很多注释,便于今后自己复习和大家的理解。


这个吃西瓜的程序意思是两个线程分别代表两个人吃西瓜的动作,这两个吃西瓜的人分别是我和超哥(超哥是我的好朋友,也就是棉花糖boss群里的那个C/C++阿超),那么吃西瓜的两人
每次每人只能吃一口,并且一人吃西瓜的时候另一个人要等着,这里用到了我们的事件对象HANDLE hTimeToEatMelon 和 CreateEvent以及设置事件状态的SetEvent(),ResetEvent()函数
要是有什么概念上的错误或者是程序上的错误或者是理解上的错误欢迎大佬们来指针。

/***********************************************************
*这个程序是小弟我昨天学习WindowsAPI时候看到了多线程的线程同步,
*是使用事件对象的标记来实现的,网上有很多博客,我也想是要自己来
*做一下试一试,采用吃瓜为实例,规定一个西瓜的容量为10口,每人只
*能吃一口每次,人人是平等的。
************************************************************/

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

HANDLE hTimeToEatMelon;                                        //事件对象设置为全局
int nMelonCapacity = 10;                                //西瓜的容量,初始设置为10口吃完的容量



//创建我吃西瓜的线程
DWORD WINAPI ThreadMe(LPVOID lpParam)
{
        printf("我准备好开始吃西瓜了\n");
        while (TRUE)
        {
                WaitForSingleObject(hTimeToEatMelon, INFINITE);                //让我吃一口瓜
                ResetEvent(hTimeToEatMelon);                                                //将事件对象设置为未受信状态,超哥先等一下吃瓜
                if (nMelonCapacity)
                {
                        nMelonCapacity--;
                        printf("我(Me)正在吃西瓜,还剩下%d口西瓜\n", nMelonCapacity);
                        Sleep(2*1000);
                }
                SetEvent(hTimeToEatMelon);                                                        //将事件对象设置为受信状态,让超哥吃瓜
                Sleep(2 * 1000);                                                                        //睡上两秒,品尝西瓜ing...
        }
        return 0;
}


//创建超哥吃瓜的线程
DWORD WINAPI ThreadChaoGe(LPVOID lpParam)
{
        printf("超哥准备好开始吃瓜了\n");
        while (TRUE)
        {
                WaitForSingleObject(hTimeToEatMelon, INFINITE);
                ResetEvent(hTimeToEatMelon);                                                //将事件对象设置为未受信状态,我要等会儿再吃瓜
                if (nMelonCapacity)
                {
                        nMelonCapacity--;
                        printf("超哥(chaoge)正在吃瓜,还剩下%d口西瓜\n", nMelonCapacity);
                        Sleep(2*1000);
                }
                SetEvent(hTimeToEatMelon);                                                        //将事件对象设置为受信状态,轮到我吃瓜了
                Sleep(2 * 1000);                                                                        //睡上两秒,超哥品尝西瓜ing...
        }
        return 0;
}



int main(void)
{
        //创建事件对象
        hTimeToEatMelon = CreateEvent(
                NULL,                                        //安全属性设置为默认
                TRUE,                                        //手动设置事件对象的状态
                TRUE,                                        //初始的时候设置事件对象的状态为标记的(受信的)
                NULL);

        if (hTimeToEatMelon == NULL || hTimeToEatMelon == INVALID_HANDLE_VALUE)
        {
                printf("CreateEvent error:%d\n", GetLastError());
                return 1;
        }

        //创建我吃瓜的线程
        HANDLE hMe = CreateThread(NULL,
                0,
                ThreadMe,
                NULL,
                0,
                NULL);
        if (hMe == INVALID_HANDLE_VALUE)
        {
                printf("CreateThread me error:%d\n", GetLastError());
                CloseHandle(hTimeToEatMelon);                        //关闭事件对象的句柄
                return 1;
        }


        //创建超哥吃瓜的线程
        HANDLE hChaoGe = CreateThread(
                NULL,
                0,
                ThreadChaoGe,
                NULL,
                0,
                NULL);
        if (hChaoGe == INVALID_HANDLE_VALUE)
        {
                printf("CreateThread ChaoGe error:%d\n", GetLastError());
                CloseHandle(hMe);
                CloseHandle(hChaoGe);
        }

        //我们把上面两个创建线程的句柄回收,但是线程还是运行着的,详情见计数器
        CloseHandle(hMe);
        CloseHandle(hChaoGe);

        //吃瓜线程在运行中
        while (TRUE)
        {
                if (nMelonCapacity == 0)
                {
                        printf("西瓜吃完了...\n");
                        CloseHandle(hTimeToEatMelon);                        //回收事件对象的句柄
                        return 0;
                }
                else
                {
                        continue;
                }
        }

        return 0;
}






               

运行结果:
我准备好开始吃西瓜了
超哥准备好开始吃瓜了
我(Me)正在吃西瓜,还剩下9口西瓜
超哥(chaoge)正在吃瓜,还剩下8口西瓜
我(Me)正在吃西瓜,还剩下7口西瓜
超哥(chaoge)正在吃瓜,还剩下6口西瓜
我(Me)正在吃西瓜,还剩下5口西瓜
超哥(chaoge)正在吃瓜,还剩下4口西瓜
我(Me)正在吃西瓜,还剩下3口西瓜
超哥(chaoge)正在吃瓜,还剩下2口西瓜
我(Me)正在吃西瓜,还剩下1口西瓜
超哥(chaoge)正在吃瓜,还剩下0口西瓜
西瓜吃完了...
请按任意键继续. . .

0xAA55 发表于 2018-6-12 11:57:57

多线程是很讲究的东西,十分复杂。稍有不慎就死锁。

并且死锁是小事,如果实际性能上不去、CPU使用率却因为自旋锁而跑满,那不仅效果不如单线程,还会给开发者一种“哇!所有的核心都用上了诶!”的错觉。

watermelon 发表于 2018-6-12 12:10:59

0xAA55 发表于 2018-6-12 11:57
多线程是很讲究的东西,十分复杂。稍有不慎就死锁。

并且死锁是小事,如果实际性能上不去、CPU使用率却因 ...

哦哦,看来多线程的东西感觉还是很有必要要好好弄弄的。

sml2 发表于 2018-6-13 16:42:39

反正我目前遇到的所有需求都不会用胎神自旋的
页: [1]
查看完整版本: 线程同步之事件对象