事件
事件,他本身可以作为通知类型,什么是通知类型,代码里会介绍到
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;
}
运行结果
他会一直阻塞起,因为没有信号,我写的是FALSE,如果设置事件为已通知,我去掉注释,然后就能运行
SetEvent就可以理解成改成有信号就行了
如果我把第二个参数改成FALSE,就相当于是一个互斥体
运行结果
就会阻塞其中一个
这里TRUE就是通知类型,可以看到每个线程都有个WaitForSingleObject函数,当他看到是一个通知类型,那这个信号就不会变成0(1为有信号),所以另一个线程也能运行,没有阻塞
当变成FALSE时,可以理解成变成了互斥体,WaitForSingleObject函数就会改变这个信号,变成0,为无信号,那另一个线程在访问的时候,发现是为无信号就只能等,知道第一个线程重置信号为止,或者等待时间超时,返回
这就是event这个对象与互斥体第一个重要的区别
线程同步
- 线程互斥:线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排他性。当有若干个线程都要使用某同一共享资源时,任何时刻最多允许一个线程去使用,其他要使用该资源的线程必须等待,直到占用资源者释放该资源
- 线程同步:线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当他没有得到另一个线程的消息时应等待直到消息到达时才被唤醒
互斥只是保证了唯一性,就是任何时刻只能有一个线程去使用这个资源,不一定有序,比如,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;
}
我把可能会产生疑问的写了注释,其他的我就不讲了,都是讲过的
可以看到这里的输出并不是按照我们想要的顺序输出的,但是两个线程都是遵守互斥的特性的,都只执行了十次(如果多运行几次,还会不同)
所以只用互斥的话,不能实现同步的效果,需要加东西
#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;
}
可以看到这里确实同步了,但是这只是从效果来说,不然的话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里加上一个输出吧,就不贴代码了
(这里我重新运行了好多次,都没有弄出视频中那中触目惊心的效果,就选了其中两个,有懂得大佬解释一下)
可以发现其中确实有浪费的现象,如果用事件得话能解决吗
代码
#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);形成了挂起自己,通知别人的现象,也就没有浪费的现象发生
消费的线程流程跟这个一样,就不多分析了
结果
这才是真正意义上的同步
总结
- 介绍了新的API
- 用不同的方式来验证了线程同步,以及优缺点