先看一下进程之间的内存空间:
-------------------------------------------------------------------------------------------------
可以看到每个进程对应一个虚拟地址空间,由虚拟地址空间映射到物理空间。当然两个虚拟地址空间也可以映射到同一个物理空间,也便实现了进程间的共享。
再看线程之前的内存空间:
-------------------------------------------------------------------------------
可以看到,一个进程内的多个线程,是共享这个进程对应的内存空间下的全局区数据,堆,代码这些的。
然后呢,问题来了:
一个简单的程序:
g_nCount=0;
_thread1 _thread2
g_nCount++; g_nCount++;
最后g_nCount的值是多少呢?
程序验证:
// #include ..............
DWORD WINAPI thread_func(LPVOID n)
{
g_nCount++;
Sleep(10);
//printf("[%d] say g_nCount = %d\n",GetCurrentThreadId(),g_nCount);
return 0;
}
int main()
{
//开5个线程
for(int j=0;j < 20;j++)
{
g_nCount=0;
HANDLE handle[100];
for(int i=0;i < 100; i++)
{
handle[i] =CreateThread(NULL,0,thread_func,NULL,0,NULL);
}
WaitForMultipleObjects(100, handle, TRUE, INFINITE);
printf("[%d]次100个线程后,g_nCount=%d\n",j,g_nCount);
Sleep(50);
}
system("pause");
}
result:
-
参考别人的程序,自己写的时候发现,如果不加外层的循环,结果是很稳定的,都是99.线程数太少时发现不了问题。现在分析造成上面结果未达到预期的原因。
上面图1-8说明了,每个线程都有自己的寄存器空间。而++自增操作在被编译器翻译成汇编语言后,变为了三条指令。可以分解为:
1>读取变量的值到寄存器
2>寄存器的值加一
3>将寄存器的值赋给变量
如果一个先启动的线程FFF取得nCount的值为10,后面的线程执行了几个(可能是优先级调度和轮转法)造成当前nCount的值已经加到比如20了。那么线程FFF现在获得优先级开始执行第三条指令,刚才的那些加的操作就被覆盖掉了。造成最后n_count的值达不到预期。至于为什么结果有0的存在,暂时也搞不懂,留下日后在研究.
造成这种现象的原因主要就是两个:线程的内存空间的共享,以及线程的优先级调度
所谓内存空间的共享,自我感觉应该就是造成互斥的原因(解决互斥,就是一个公用的变量同一时间只能被一个线程执行,也就是原子操作)
而优先级调度,就是造成同步的原因了(解决同步,就是要等待,等别人执行完关键语句)
本文的重点来了:信号量,互斥量,临界区,条件变量,读写锁
二元信号量是最简单的一种锁,它只有两种状态,占用与非占用。它适合只被唯一一个线程独占访问的资源。当二元信号量处于非占用状态时,第一个试图获取该信号量的线程会获得该锁,并将二元信号量置为占用状态,此后其他试图占用该锁的线程将会等待,知道该锁不给释放。
对于允许多个线程并发访问的资源,多元信号量简称信号量。一个初始值为N的信号量允许N个线程并发访问。
windows下的函数说明:
使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:
-----------------------函数功能:创建信号量
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
LONG lInitialCount, // 初始计数
LONG lMaximumCount, // 最大计数
LPCTSTR lpName // 对象名指针
);
-----------------函数功能:打开信号量
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 信号量名
);
------------------递增信号量的当前资源计数
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 信号量句柄
LONG lReleaseCount, // 计数递增数量 ,必须大于0且不超过最大资源数量
LPLONG lpPreviousCount // 第三个参数可以用来传出先前的资源计数,设为NULL表示不需要传
出。
);
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); // 监测单个的同步对象状态
DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE * lpHandles,BOOL bWaitAll,DWORD dwMilliseconds); // 同时监测多个同步对象
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
执行waitfor函数进入信号量保护区,lInitialCount信号量的值减一
如果信号量的值小于0,则进入等待状态,否则继续执行
执行ReleaseSemaphore函数释放信号量,lInitialCount信号量的值加一
如果信号量的值小于1,唤醒一个等待中的线程
值得注意的一点,可以create信号量的时候,初始值设为大于1,这样后面就可以直接执行ReleaseSemaphore,释放信号量,而不一定要先wait进入信号量了。
用信号量解决一个简单的同步问题:
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstdio>
#include <cstdio> #include <iostream>
#include <iostream> #include <process.h>
#include <windows.h> #include <windows.h>
using namespace std; using namespace std;
int g_nCount=0; int g_nCount=0;
HANDLE Semaphore;
DWORD WINAPI thread_func(LPVOID n) HANDLE g_hThreadParameter;
{ unsigned int __stdcall thread_func(LPVOID n)
int num=*(int *)n; {
printf("第[%d]个线程已经打开!\n",num); int num=*(int *)n;
Sleep(10); printf("第[%d]个线程已经打开!\n",num);
return 0; ReleaseSemaphore(Semaphore, 1, NULL);//信号量++
} return 0;
int main() }
{ int main()
HANDLE handle; {
for(int i=0;i < 10; i++) HANDLE handle[10];
{ Semaphore = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问
handle =CreateThread(NULL,0,thread_func,&i,0,NULL); g_hThreadParameter = CreateSemaphore(NULL, 0, 1, NULL);
WaitForSingleObject( &handle, INFINITE); int i=0;
} while( i < 10)
system("pause"); {
} handle[i] =(HANDLE)_beginthreadex(NULL,0,thread_func,&i,0,NULL);
WaitForSingleObject( Semaphore, INFINITE);
i++;
}
WaitForMultipleObjects(10, handle, TRUE, INFINITE);
system("pause");
}
结果对比:
这个程序的最大并发信号量设置的是1,相当于二元信号量了。
初始值为0,主线程进入,值减一,小于0,等待。
直到一个线程执行,释放,值加一。值小于1,唤起别的进程。
。。
信号量的这一特性可以用在套接字编程,每一个线程wait,然后release。并发数的大小就是最大连接数。
在总结一点,create的时候初始值的大小设置为0还是最大并发数或者其他,取决于后面的wait和release顺序的安排。
这里的wait,只要线程执行到wait的地方,wait就会被执行,只不过wait会根据信号量的值来决定是等待还是继续向西执行,就想是一个路障一样。(wait是在进入wait函数后等待的)
信号量也可以解决互斥,但效率看起来不怎么高.(并发数设置为1,每个子线程都等待,一次进入一个,就相当于原子操作了)
open函数,网上的资料很少用到,暂时也就不管了。不过open函数的参数有信号量的名称,用来进程间的线程通信.
-------------------------------------------------------------------------------------------互斥量-------------------------------------------------------------------------------------------------------
互斥量和二元信号量很类似,资源仅同时允许一个线程访问。但和信号量不同的时,信号量在整个系统可以被任意线程获取并释放 。而互斥量要求哪个线程获取了,便只能由这个线程释放。因为互斥量记录了线程的id和名称。名称使它可以在不同进程间访问。id标记了只能被当前记录的线程释放。
由于只能被一个线程获取和释放,而且获取和释放的线程是同一个线程。所以不能用互斥量区等待解决同步问题。
但是互斥量可以解决互斥问题:
引用一些资料:
互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问。互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源。使用互斥量Mutex主要将用到四个函数。下面是这些函数的原型和使用说明。
第一个CreateMutex
函数功能:创建互斥量(注意与事件Event的创建函数对比)
函数原型:
HANDLECreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes,
BOOLbInitialOwner,
LPCTSTRlpName
);
函数说明:
第一个参数表示安全控制,一般直接传入NULL。
第二个参数用来确定互斥量的初始拥有者。如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。
第三个参数用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
第二个打开互斥量
函数原型:
HANDLEOpenMutex(
DWORDdwDesiredAccess,
BOOLbInheritHandle,
LPCTSTRlpName //名称
);
函数说明:
第一个参数表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数表示互斥量句柄继承性,一般传入TRUE即可。
第三个参数表示名称。某一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
第三个触发互斥量
函数原型:
BOOLReleaseMutex (HANDLEhMutex)
函数说明:
访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示自己已经结束访问,其它线程可以开始访问了。
最后一个清理互斥量
由于互斥量是内核对象,因此使用CloseHandle()就可以(这一点所有内核对象都一样)。
---------------------------------------------------------------------------------------
可以发现互斥跟信号量的函数及其相似,用法其实也很相似,下面用代码解决一个简单的互斥:
//经典线程同步问题 互斥量Mutex
#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_hThreadParameter;
int main()
{
g_hThreadParameter = CreateMutex(NULL, FALSE, NULL);
HANDLE handle[THREAD_NUM];
g_nNum = 0;
int i = 0;
while (i < THREAD_NUM)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
i++;
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
CloseHandle(g_hThreadParameter);
for (i = 0; i < THREAD_NUM; i++)
CloseHandle(handle[i]);
return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
WaitForSingleObject(g_hThreadParameter, INFINITE);
int nThreadNum = *(int *)pPM;
Sleep(50);//some work should to do
g_nNum++;
Sleep(0);//some work should to do
printf("全局资源值为%d\n", nThreadNum, g_nNum);
ReleaseMutex(g_hThreadParameter);//触发互斥量
return 0;
}
总结:互斥量就相当于二元信号量,不过由于互斥量记录了线程id,只能由同一个线程获取和释放。
所以互斥量只能用于互斥。
而信号量既可以同步又可以互斥。
信号量,互斥量都可以跨进程。
-----------------------------------------------------------------------------临界区(windows下好像也有叫关键段)------------------------------------------------------------------------------
临界区势必互斥量更加严格的同步手段。在术语上,吧临界区的锁的获取称为进入临界区,释放称为离开临界区。临界区和互斥量与信号量的区别在于,互斥量和信号量在系统的任何进程内都是可见的,也就是说,一个进程创建了一个互斥量和信号量,另一个进程试图去获取该锁是合法的。然而,临界区的作用范围仅限于本线程内。
拷贝下函数:
函数功能:初始化
函数原型:
voidInitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:定义关键段变量后必须先初始化。
函数功能:进入关键区域
函数原型:
voidEnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。
函数功能:离开关关键区域
函数原型:
voidLeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数功能:销毁
函数原型:
voidDeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:用完之后记得销毁。
代码:
#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//关键段变量声明
CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
int main()
{
printf(" 经典线程同步 关键段\n");
printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
//关键段初始化
InitializeCriticalSection(&g_csThreadParameter);
InitializeCriticalSection(&g_csThreadCode);
HANDLE handle[THREAD_NUM];
g_nNum = 0;
int i = 0;
while (i < THREAD_NUM)
{
EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
++i;
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode);
DeleteCriticalSection(&g_csThreadParameter);
return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
int nThreadNum = *(int *)pPM;
LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域
Sleep(50);//some work should to do
EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域
g_nNum++;
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域
return 0;
}
可以看到临界区可以解决互斥,但不能解决同步.
参考下上面提到的那篇文章:
先找到关键段CRITICAL_SECTION的定义吧,它在WinBase.h中被定义成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTION在WinNT.h中声明,它其实是个结构体:
typedef struct_RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
LONGLockCount;
LONGRecursionCount;
HANDLEOwningThread; // from the thread's ClientId->UniqueThread
HANDLELockSemaphore;
DWORDSpinCount;
}RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
各个参数的解释如下:
第一个参数:PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
调试用的。
第二个参数:LONGLockCount;
初始化为-1,n表示有n个线程在等待。
第三个参数:LONGRecursionCount;
表示该关键段的拥有线程对此资源获得关键段次数,初为0。
第四个参数:HANDLEOwningThread;
即拥有该关键段的线程句柄,微软对其注释为——from the thread's ClientId->UniqueThread
第五个参数:HANDLELockSemaphore;
实际上是一个自复位事件。
第六个参数:DWORDSpinCount;
旋转锁的设置,单CPU下忽略
----------------------------------------------------------------------------------------------------------------临界区的第一个函数是init,init初始化的是一个CRITICAL_SECTION类型的值。后面的enter和leave会用到这个结构体。
而互斥量和信号量使用create,返回一个handle句柄。后面wait和release的参数也要用这个句柄。
临界区创建的这个结构体,记录了enter时的线程,以及线程进入的次数。线程的句柄。
enter时候的线程可以重复进入,但别的进程不同进入已经被占用的临界区。
值得注意的是,临界区只能在当前进程内使用。不能跨进程。
------------------------------------------------------------------------------------------事件-----------------------------------------------------------------------------------------------------
事件好像是windows下特有的,就不过多深入了。拷贝下别人的文章做个记录:
上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步。本篇介绍用事件Event来尝试解决这个线程同步问题。
首先介绍下如何使用事件。事件Event实际上是个内核对象,它的使用非常方便。下面列出一些常用的函数。
第一个 CreateEvent
函数功能:创建事件
函数原型:
HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset,
BOOLbInitialState,
LPCTSTRlpName
);
函数说明:
第一个参数表示安全控制,一般直接传入NULL。
第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。
第三个参数表示事件的初始状态,传入TRUR表示已触发。
第四个参数表示事件的名称,传入NULL表示匿名事件。
第二个 OpenEvent
函数功能:根据名称获得一个事件句柄。
函数原型:
HANDLEOpenEvent(
DWORDdwDesiredAccess,
BOOLbInheritHandle,
LPCTSTRlpName //名称
);
函数说明:
第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数表示事件句柄继承性,一般传入TRUE即可。
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
第三个SetEvent
函数功能:触发事件
函数原型:BOOLSetEvent(HANDLEhEvent);
函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
第四个ResetEvent
函数功能:将事件设为末触发
函数原型:BOOLResetEvent(HANDLEhEvent);
最后一个事件的清理与销毁
由于事件是内核对象,因此使用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()
- {
- printf(" 经典线程同步 事件Event\n");
- printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
- //初始化事件和关键段 自动置位,初始无触发的匿名事件
- 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);//some work should to do
- EnterCriticalSection(&g_csThreadCode);
- g_nNum++;
- Sleep(0);//some work should to do
- printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
- LeaveCriticalSection(&g_csThreadCode);
- return 0;
- }
运行结果如下图:
可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。
现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了PulseEvent()函数,所以接下来再继续深挖下事件Event,看看它还有什么秘密没。
先来看看这个函数的原形:
第五个PulseEvent
函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。
函数原型:BOOLPulseEvent(HANDLEhEvent);
函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:
1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。
此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。
下面对这个触发一个事件脉冲PulseEvent ()写一个例子,主线程启动7个子线程,其中有5个线程Sleep(10)后对一事件调用等待函数(称为快线程),另有2个线程Sleep(100)后也对该事件调用等待函数(称为慢线程)。主线程启动所有子线程后再Sleep(50)保证有5个快线程都正处于等待状态中。此时若主线程触发一个事件脉冲,那么对于手动置位事件,这5个线程都将顺利执行下去。对于自动置位事件,这5个线程中会有中一个顺利执行下去。而不论手动置位事件还是自动置位事件,那2个慢线程由于Sleep(100)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。
代码如下:
- //使用PluseEvent()函数
- #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 启动\n", (PSTR)pPM);
- WaitForSingleObject(g_hThreadEvent, INFINITE);
- printf("%s 等到事件被触发 顺利结束\n", (PSTR)pPM);
- return 0;
- }
- //慢线程
- unsigned int __stdcall SlowThreadFun(void *pPM)
- {
- Sleep(100);
- printf("%s 启动\n", (PSTR)pPM);
- WaitForSingleObject(g_hThreadEvent, INFINITE);
- printf("%s 等到事件被触发 顺利结束\n", (PSTR)pPM);
- return 0;
- }
- int main()
- {
- printf(" 使用PluseEvent()函数\n");
- printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
- BOOL bManualReset = FALSE;
- //创建事件 第二个参数手动置位TRUE,自动置位FALSE
- g_hThreadEvent = CreateEvent(NULL, bManualReset, FALSE, NULL);
- if (bManualReset == TRUE)
- printf("当前使用手动置位事件\n");
- else
- printf("当前使用自动置位事件\n");
- 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()\n");
- PulseEvent(g_hThreadEvent);//调用PulseEvent()就相当于同时调用下面二句
- //SetEvent(g_hThreadEvent);
- //ResetEvent(g_hThreadEvent);
- Sleep(3000);
- printf("时间到,主线程结束运行\n");
- CloseHandle(g_hThreadEvent);
- return 0;
- }
对自动置位事件,运行结果如下:
对手动置位事件,运行结果如下:
最后总结下事件Event
1.事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
3.事件可以解决线程间同步问题,因此也能解决互斥问题。