在多线程程序设计里,线程同步是非常重要的,基本的方法有
1.互斥对象
2.事件对象
3.信号量
4.临界区
在说到线程同步的时候就不能不介绍一下WAIT函数
WAIT函数是在等待的对象处于激发状态时候才能返回,常见的WAIT函数有
WaitForSingleObject,WaitForMultipleObjects,MsgWaitForMultipleObjects
所以了解等待对象的何时处于激发状态是正确使用WAIT函数的关键,在介绍同步方法的时候,我会简单的说明。
在介绍这四种同步方法的时候,我使用了一个很经典的售票的例子,
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2];
hThread[0] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[1] = CreateThread(NULL,0,SellTicket,0,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
return 0;
}
DWORD __stdcall SellTicket(LPVOID lpParam)
{
while(Ticket>0)
{
cout<<"The "<<Ticket--<<" Ticker sold!"<<endl;
}
return 0;
}
对于这个例子我们都知道如果不采用线程同步的方法,两个线程之间就会发生一些无法预料的事情。
对我来说最习惯使用的是事件对象来实现线程的同步,设定一个全局的事件对象
HANDLE h_Event = NULL;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2];
h_Event = CreateEvent(NULL,FALSE,TRUE,NULL);
hThread[0] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[1] = CreateThread(NULL,0,SellTicket,0,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(h_Event);
return 0;
}
DWORD __stdcall SellTicket(LPVOID lpParam)
{
WaitForSingleObject(h_Event,INFINITE);
while(Ticket>0)
{
cout<<"The "<<Ticket--<<" Ticker sold!"<<endl;
}
SetEvent(h_Event);
return 0;
}
这样如果一个线程没有返回时,另一个线程就会被进入等待状态,而不能进行售票的动作。最后我们要对事件对象调用CloseHandle()销毁事件对象。
临界区域变量不属于内核对象,它也能对包含同一个资源的代码段进行同步保护,
它相当于占有了那一段代码的所有权,当它还没有离开的时候,别的线程就进不去那段代码段。
(感觉这也是多线程设计的一个优点,每个线程都可以进程的代码段资源)
CRITICAL_SECTION cs;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2];
InitializeCriticalSection(&cs);
hThread[0] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[1] = CreateThread(NULL,0,SellTicket,0,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
DeleteCriticalSection(&cs);
return 0;
}
DWORD __stdcall SellTicket(LPVOID lpParam)
{
EnterCriticalSection(&cs)
while(Ticket>0)
{
cout<<"The "<<Ticket--<<" Ticker sold!"<<endl;
}
LeaveCriticalSection(&cs);
return 0;
}
在使用临界区域变量的时候要进行初始化,当不使用的时候要对销毁掉临界区域变量。
如果以一个线程进入临界区域,那么一直重复的进入该临界区域。(这里说的该临界区域是指同一个临界区域变量所有的代码段,那些代码段可以分别在不同的函数里,不需要是连续的)
由于临界区域变量不是内核对象,加入一个线程进入了之后就死了,而没有离开临界区结果就会锁住那块代码段,并且系统也不能清除掉临界区域变量,最后的结果可能就是程序挂在那,求生不得,求死不能。
/*------------------------------这里还有一个使用临界区变量思索的问题-------------------------------
---------------------------------回过头再来讨论这个问题----------------------------------------------
--------------主要是因为临界区变量不是内核对象我们无法想其他内核对象一样,通过WAIT函数知道是否被激发------*/
所以我要介绍下一个对象是互斥对象,它是内核对象,可以解决临界区域变量的一些问题,但它也有一些问题(别着急,那些问题我也正在思考!)
互斥对象可以对资源同步操作,互斥对象在没有任何线程拥有时候处于激发状态,准确的说是在没有任何线程拥有时,被Wait..函数等待的时候,短暂的处于激发状态,让Wait...函数返回,同时该线程拥有互斥对象的所有权,记住,一个时间内只能有一个线程拥有互斥对象。
HANDLE h_Mutex;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[2];
h_Mutex = CreateMutex(NULL,FALSE,NULL);
hThread[0] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[1] = CreateThread(NULL,0,SellTicket,0,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(h_Mutex);
return 0;
}
DWORD __stdcall SellTicket(LPVOID lpParam)
{
WaitForSingleObject(h_Mutex,INFINITE);
while(Ticket>0)
{
cout<<"The "<<Ticket--<<" Ticker sold!"<<endl;
}
ReleaseMutex(h_Mutex);
return 0;
}
当不再需要互斥对象的时候,要释放互斥对象。
如果一个线程没有释放那个互斥对象,那么那个互斥对象会被舍弃处理,如果有其他线程等待,WAIT函数会返回WAIT_OBANDONED_0到WAIT_OBANDONED_0+N之间的一个数,我们就能知道那个互斥对象被舍弃了
问题也产生了:知道了哪个互斥对象被舍弃了又能怎么样呢?这个一个问题,那些受保护的数据又该怎么保护,比起临界区来说,至少程序不会挂在那,因为WAIT函数至少还能返回,但是安全问题依然没有解决。
信号量是我感觉比较麻烦的一个,semaphore创建的时候可以指定对一种资源进行占有的线程数,就是说信号量可以锁你指定的次数,互斥对象其实是它的特殊情况,当对一份资源进行占有的线程数为1的时候其实就是互斥对象了。当一个线程锁住一份资源的时候,信号量就会减1,当减到0的时候,就要等某个线程释放了那个资源其他的线程才能对资源进行占有了。
为什么需要信号量呢,因为有时候我们的某种资源是很多很多滴,太多了,以至于为每个资源都捆绑一个互斥对象显得太麻烦,麻烦就是个问题,谁也不想太麻烦。其实还有其他原因。。。不想说了。
我要想一个比较什么例子来使用信号量,写个售票和退票的例子也许是个不错的尝试
int Ticket = 100;
HANDLE h_SemaphoreSell;
HANDLE h_SemaphoreDishonor;
HANDLE h_Event;
DWORD dwStart;
DWORD __stdcall SellTicket(LPVOID lpParam);
DWORD __stdcall DishonorTicket(LPVOID lpParam);
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread[6];
dwStart = GetTickCount();
h_SemaphoreSell = CreateSemaphore(NULL,50,100,NULL);
h_SemaphoreDishonor = CreateSemaphore(NULL,0,100,NULL);
h_Event = CreateEvent(NULL,FALSE,TRUE,NULL);
hThread[0] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[1] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[2] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[3] = CreateThread(NULL,0,SellTicket,0,0,NULL);
hThread[4] = CreateThread(NULL,0,DishonorTicket,0,0,NULL);
hThread[5] = CreateThread(NULL,0,DishonorTicket,0,0,NULL);
WaitForMultipleObjects(6,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(hThread[2]);
CloseHandle(hThread[3]);
CloseHandle(hThread[4]);
CloseHandle(hThread[5]);
CloseHandle(h_SemaphoreSell);
CloseHandle(h_SemaphoreDishonor);
return 0;
}
DWORD __stdcall SellTicket(LPVOID lpParam)
{
DWORD dwResult;
while(1)
{
WaitForSingleObject(h_Event,INFINITE);
dwResult = WaitForSingleObject(h_SemaphoreSell,3000);
if(WAIT_OBJECT_0 == dwResult)
{
cout<<"The "<<Ticket--<<" Ticket sold!"<<endl;
ReleaseSemaphore(h_SemaphoreDishonor,1,NULL);
}
else if(WAIT_TIMEOUT == dwResult)
{
;
}
SetEvent(h_Event);
}
return 0;
}
DWORD __stdcall DishonorTicket(LPVOID lpParam)
{
while(1)
{
WaitForSingleObject(h_Event,INFINITE);
WaitForSingleObject(h_SemaphoreDishonor,INFINITE);
cout<<"The "<<++Ticket<<" Ticket dishonored"<<endl;
ReleaseSemaphore(h_SemaphoreSell,1,NULL);
SetEvent(h_Event);
}
return 0;
}
我设了2个信号量,1个是售票的,1个是退票的,开始的时候我把售票信号量初始值设为50(那些走关系的就先把这50张票拿走了,就把50张拿来出售了,的我们只能排队慢慢等了)当卖出一张票的时候售票信号量就锁住,同时释放退票信号量,
同理,当退出一张票的时候退票信号量就锁住,同时释放售票信号量。我创建了4个卖票线程,2个退票线程,毕竟是买票更不好买啊!
当卖完50张的时候,就要等待有人退票才能买到票了,这个例子不怎么好,因为最后总是一个人在卖第51张票,又在不断的退第51张票。继续努力中。。。。。。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jxcyly1985/archive/2008/09/05/2886793.aspx