#include<iostream>
#include <windows.h>
using namespace std;
DWORD WINAPI Fun1Proc(
LPVOID lpParameter);
DWORD WINAPI Fun2Proc(
LPVOID lpParamter);
HANDLE g_hEvent;
int tickets = 100;
int main()
{
HANDLE hThread1;
HANDLE hThread2;
g_hEvent = CreateEvent(NULL, TRUE, false, NULL);
SetEvent(g_hEvent);
hThread1 = CreateThread(false, 0, Fun1Proc, NULL, 0, NULL);
hThread2 = CreateThread(false, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
Sleep(4000);
CloseHandle(g_hEvent);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
ResetEvent(g_hEvent);
if (tickets>0)
{
Sleep(1);
cout << "thread1 sell ticket:" << tickets-- << endl;
SetEvent(g_hEvent);
}
else
{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
ResetEvent(g_hEvent);
if (tickets>0)
{
Sleep(1);
cout << "thread2 sell ticket:" << tickets-- << endl;
SetEvent(g_hEvent);
}
else
{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
g_hEvent = CreateEvent(NULL, TRUE, false, NULL);
创建的一个人工重置事件对象,当线程等待到该对象的说有权之后,需要调用ResetEven函数手动将该对象设置为无信号状态。
这样才能实现两个线程之间的同步。
但是运行之后发现出现thread1(2) sell ticket 0;说明程序出现问题。说明程序仍然没有实现线程间的同步。上述做法存在两个原因:
1.在单CPU情况下,只能运行一个线程,若1线程运行,得到事件对象,但正好这个时候时间片停止了,没来的及执行ResetEvet,将事件对象转为无信号状态,这时执行线程2,线程2就得到该事件对象,也就是说两个线程同时进入保护程序。,结果不可预料。
2.多CPU情况下,线程1,2可以同时进行,这时在调用SetEvent函数将其设置为有信号状态已经不起作用了,因为两个线程同时进入所保护程序,结果不可预料。
为了实现线程间的同步,也不应该使用人工重置的事件对象,而应该使用自动重置的事件对象
g_hEvent = CreateEvent(NULL, false, false, NULL);
创建的一个自动重置事件对象
当自动重置的事件得到通知时,等待该事件的线程中只有一个线程变得可调度线程,,若线程1得到事件对象,因为他是一个自动事件,所以操作系统会将该事件对象设置为无信号状态。
#include<iostream>
#include <windows.h>
using namespace std;
DWORD WINAPI Fun1Proc(
LPVOID lpParameter);
DWORD WINAPI Fun2Proc(
LPVOID lpParamter);
HANDLE g_hEvent;
int tickets = 100;
int main()
{
HANDLE hThread1;
HANDLE hThread2;
g_hEvent = CreateEvent(NULL, false, false, NULL);
SetEvent(g_hEvent);
hThread1 = CreateThread(false, 0, Fun1Proc, NULL, 0, NULL);
hThread2 = CreateThread(false, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
Sleep(4000);
CloseHandle(g_hEvent);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
//ResetEvent(g_hEvent);
if (tickets>0)
{
Sleep(1);
cout << "thread1 sell ticket:" << tickets-- << endl;
SetEvent(g_hEvent);
}
else
{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);
//ResetEvent(g_hEvent);
if (tickets>0)
{
Sleep(1);
cout << "thread2 sell ticket:" << tickets-- << endl;
SetEvent(g_hEvent);
}
else
{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
当人工重置的事件对象得到通知时,该事件对象的所有线程均变为可调度线程:当一个自动重置的对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程,同时操作系统会将该事件对象设置无信号状态,这样,当对所有报读的代码执行完成之后,需要调用SetEvent函数将该事件对象设置为有信号状态。而人工重置的事件对象,在一个线程得到该事件对象之后,操作系统并不会将该事件对象设置为无信号状态,除非显示的调用ResetEvent函数将其设置为无信号状态,否则该对象会一直是有信号状态。
关键代码段
#include <iostream>
#include <windows.h>
using namespace std;
CRITICAL_SECTION g_sr;
DWORD WINAPI Fun1Proc(
LPVOID lpParamter
);
DWORD WINAPI Fun2Proc(
LPVOID lpParamter
);
int main()
{
HANDLE pThread1;
HANDLE pThread2;
InitializeCriticalSection(&g_sr);
pThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
pThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(pThread1);
CloseHandle(pThread2);
Sleep(4000);
DeleteCriticalSection(&g_sr);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParamter)
{
while (TRUE)
{
EnterCriticalSection(&g_sr);
Sleep(1);
cout << "Fun1Proc" << endl;
LeaveCriticalSection(&g_sr);
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParamter)
{
while (TRUE)
{
EnterCriticalSection(&g_sr);
Sleep(1);
cout << "Fun2Proc" << endl;
LeaveCriticalSection(&g_sr);
}
return 0;
}
互斥对象,事件对象,关键代码段的比较
互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时,无法设定超时值。
通常,在编写多线程程序并需要实现线程同步时,首选关键代码段,由于它的使用比较简单,如果是在MFC程序中使用的话,可以在类的构造函数中调用InitializeCriticalSection函数,在该类的析构函数中调用DeleteCriticalSection函数,在所需保护的代码前面调用EnterCriticalSection函数,在访问完所需保护的资源后,调用LeaveCriticalSection函数。可见,关键代码段在使用上是非常方便的,但有几点需要注意:一是在程序中调用了EnterCriticalSection后,一定要相应的调用LeaveCriticalSection函数,否则其他等待该临界区对象所有权的线程将无法执行。二是如果访问关键代码段时,使用了多个临界区对象,就要注意防止线程死锁的发生。另外,如果需要在多个线程间的各个线程间实现同步的话,可以使用互斥对象和事件对象。
前提:假设有个经理,下面有5个项目组.经理同时最多能接5个项目,每个项目组一次只能做一个项目。
信标Semaphore:信标相当于,5个项目组都争先恐后的争夺项目。如果经理有5个项目,那么5个项目组都可以做。如果经理有3个项目,那就有2个项目没事情做,在等待。如果经理没有项目,那么5个组,都在闲着。
事件Event:相当于,外面有个项目,经理把项目接回来了。站在门口大喊:有项目了。喊完了,经理就不管了这个项目又没有人处理。
互斥Mutex:相当于,经理手上有1个项目。下面的组,主动地争夺项目,谁抢到谁就有事情做,没抢到的就得待着。
形象的理解:
关键段与互斥量都有“线程所有权”概念,可以将“线程所有权”理解成旅馆的房卡,在旅馆前台登记名字拥有房卡后是可以多次进出房间的,其它人则无法进入直到你交出房卡。每个线程必须先通过EnterCriticalSection或WaitForSingleObject来尝试获得“线程所有权”才能调用LeaveCriticalSection或ReleaseMutex。否则会调用失败,这就相当于伪造房卡去办理退房手续——由于登记本上没有你的名字所以会被拒绝。 互斥量能很好的处理“遗弃”情况,因此在多进程之间可以放心的使用。
事件与信号量相当于管停车位的,信号量的大小相当于停车位容量多大,比如一共有5个停车位,来了3辆车,那可以直接进去,但是现在又来了3辆车,只能按先后顺序进去,有一辆车得等,直到停车场上有车开走了,这辆车才能进去。而事件相当于只有一个停车位的停车场。