利用互斥对象实现线程同步的实例说明
2017-01-13 22:17
507 查看
多线程编程中,如果我们稍有不注意,很容易出现一些意想不到的结果,主要原因就是多线程的同步处理;我们需要保证多个线程在共同运行时,进行对应资源的同步处理,保证一个线程访问共享资源时,其他线程不能访问该资源,这里将使用互斥对象实现线程同步;
在“认识多线程”这篇文章中有个实例2,该实例给出了两个线程共同完成g_Index的计数,并打印对应的线程标识;最终的运行结果存在不符合预期的地方;原因就是没有对g_Index这个共享资源进行保护;本节将利用互斥对象实现线程同步,解决这个问题;
互斥对象包含一个使用数量,一个线程ID,一个计数器。其中ID用于标识哪个线程当前拥有这个互斥对象,计数器用于标识该线程拥有互斥对象的次数。
这里会用到CreateMutex、ReleaseMutex、WaitForSingleObject这三个函数,利用这三个函数可以完成线程同步,下面介绍这几个函数;
创建或者打开一个已经命名的或者匿名的互斥对象
参数:
lpMutexAttributes
一个指向LPSECURITY_ATTRIBUTES结构的指针,我们一般设置该值为NULL,表示让互斥对象使用默认的安全性;
bInitialOwner
指定互斥对象初始的拥有者,TRUE:创建者对象的线程获得该对象的使用权,内部计数器加1,否则,该线程不获得互斥对象的所有权;
lpName
指定互斥对象的名称,如果此参数为NULL,表示创建一个匿名的互斥对象;
参数:
hMutex 需要释放的互斥对象的句柄
当前线程调用了该函数,则其他线程就有机会获得该对象的所有权,从而获得共享资源的访问;
参数:
hHandle
所请求互斥对象的句柄;一旦互斥对象处于有信号状态,该函数就返回。如果该互斥对象一致处于无状态对象,函数会一致等待,该线程暂停执行;
dwMilliseconds
指定等待的时间间隔,以毫秒为单位。
如果该参数为0表示测试该对象的状态就立即返回,不关心线程是否获得所有权,相当于没有进行线程同步处理;
如果该参数为INFINITE表示函数会一直等待,直到等待的对象处于有信号状态才会返回,这里能保证资源的独占性;
WaitForSingleObject函数返回的两种情况,其返回值有三个WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED:
1)指定的对象变成有信号状态
2)指定的等待时间间隔已过;
hMetex = CreateMutex(NULL, TRUE, NULL);运行结果:
分析:
wMain主线程获得这个互斥对象的执行权,并且没有释放这个互斥对象,其他线程在WaitForSingleObject时,该互斥对象都属于未通知状态,因此Thread1和Thread2都不会执行;
第一点说明:
对互斥对象来说,它是唯一与线程相关的内核对象;当主线程拥有互斥对象时,操作系统将互斥对象的线程ID设置为主线程的ID。
线程1在调用ReleaseMutex函数时,操作系统会判断当前线程的线程ID和互斥对象中的线程ID是否相等,只有相等才能正在释放;由于主线没有释放,互斥对象的线程ID是主线程的ID,导致其他线程没有执行的机会。对于互斥对象来说,必须是谁拥有谁完成释放;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread1 = NULL;
HANDLE hThread2 = NULL;
//创建互斥对象,主线程有执行权
hMetex = CreateMutex(NULL, TRUE, NULL);
WaitForSingleObject(hMetex,INFINITE);
//创建新的线程
hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
//无须对新线程设置优先级等操作,关闭之
//良好的编码习惯
CloseHandle(hThread1);
CloseHandle(hThread2);
//CreateMutex创建时,主线程获得所有权,计数器+1
ReleaseMutex(hMetex);
//同一个线程内调用WaitForSingleObject,即此刻互斥对象处于未通知状态
//调用线程也会获得执行权,计数器再+1
ReleaseMutex(hMetex);
//主线程放弃执行全力
Sleep(5000);
return 0;
}
第二点说明:
我们在调用WaitForSingleObject函数时,若请求的线程ID和互斥对象内部的线程ID是相同的,即使此时此互斥对象处于未通知状态,该线程还可以请求到该回车对象的所有权,计数器则会再加1;所以实例4中需要释放互斥对象两次才行;
当一个线程已经终止时(比如线程return),操作系统自动会帮助我们将互斥对象的线程ID设置为0,并且将引用计数器设置为0;
//新线程的起始地址
DWORD WINAPI Thread1Proc(LPVOID lpParameter)
{
//等待线程的执行权,无限期等待
//当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行
WaitForSingleObject(hMetex,INFINITE);
cout << "Thread1 is ruuning" << endl;
//线程结束,自动释放互斥变量,使其处于已通知状态
return 0;
}
//新线程的起始地址
DWORD WINAPI Thread2Proc(LPVOID lpParameter)
{
WaitForSingleObject(hMetex,INFINITE);
cout << "Thread2 is ruuning" << endl;
//线程结束,自动释放互斥变量,使其处于已通知状态
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread1 = NULL;
HANDLE hThread2 = NULL;
//创建互斥对象,主线程没有执行权
hMetex = CreateMutex(NULL, FALSE, NULL);
//创建新的线程
hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
//无须对新线程设置优先级等操作,关闭之
//良好的编码习惯
CloseHandle(hThread1);
CloseHandle(hThread2);
//主线程放弃执行权力
Sleep(5000);
return 0;
}
在“认识多线程”这篇文章中有个实例2,该实例给出了两个线程共同完成g_Index的计数,并打印对应的线程标识;最终的运行结果存在不符合预期的地方;原因就是没有对g_Index这个共享资源进行保护;本节将利用互斥对象实现线程同步,解决这个问题;
互斥对象
互斥对象(Mutex)属于内核对象,它能确保线程拥有对单个资源的互斥范围权利,即线程A正在拥有资源Z,线程B恰好也要使用资源Z,则线程B会等到线程A使用完资源后,才去使用资源Z;互斥对象包含一个使用数量,一个线程ID,一个计数器。其中ID用于标识哪个线程当前拥有这个互斥对象,计数器用于标识该线程拥有互斥对象的次数。
这里会用到CreateMutex、ReleaseMutex、WaitForSingleObject这三个函数,利用这三个函数可以完成线程同步,下面介绍这几个函数;
CreateMutex
HANDLE WINAPI CreateMutex( __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner, __in_opt LPCTSTR lpName );功能:
创建或者打开一个已经命名的或者匿名的互斥对象
参数:
lpMutexAttributes
一个指向LPSECURITY_ATTRIBUTES结构的指针,我们一般设置该值为NULL,表示让互斥对象使用默认的安全性;
bInitialOwner
指定互斥对象初始的拥有者,TRUE:创建者对象的线程获得该对象的使用权,内部计数器加1,否则,该线程不获得互斥对象的所有权;
lpName
指定互斥对象的名称,如果此参数为NULL,表示创建一个匿名的互斥对象;
ReleaseMutex
当我们对共享资源访问后,我们需要释放该对象的所有权,让这个互斥对象处于已通知状态;此时可以调用ReleaseMutex函数,调用该函数相当于互斥对象的计数器减1,其函数声明如下:BOOL WINAPI ReleaseMutex(__in HANDLE hMutex);
参数:
hMutex 需要释放的互斥对象的句柄
当前线程调用了该函数,则其他线程就有机会获得该对象的所有权,从而获得共享资源的访问;
WaitForSingleObject
线程若要获得互斥对象的所有权,则必须主动发出请求才能获得该互斥对象的所有权;获得对象的所有权,可以调用WaitForSingleObject函数来实现,若成功获得互斥对象的执行权,该互斥对象的计数器则增加1,其函数声明如下:DWORD WINAPI WaitForSingleObject( __in HANDLE hHandle, __in DWORD dwMilliseconds );
参数:
hHandle
所请求互斥对象的句柄;一旦互斥对象处于有信号状态,该函数就返回。如果该互斥对象一致处于无状态对象,函数会一致等待,该线程暂停执行;
dwMilliseconds
指定等待的时间间隔,以毫秒为单位。
如果该参数为0表示测试该对象的状态就立即返回,不关心线程是否获得所有权,相当于没有进行线程同步处理;
如果该参数为INFINITE表示函数会一直等待,直到等待的对象处于有信号状态才会返回,这里能保证资源的独占性;
WaitForSingleObject函数返回的两种情况,其返回值有三个WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED:
1)指定的对象变成有信号状态
2)指定的等待时间间隔已过;
实例1
现在利用互斥对象实现符合线程安全的计数器打印,其具体代码如下:#include "stdafx.h" #include <windows.h> #include <iostream> //#include <afxmt.h> using namespace std; int g_nIndex = 0; const int nMaxCnt = 50; //CCriticalSection csIndexLock; HANDLE hMetex = NULL; //新线程的起始地址 DWORD WINAPI Thread1Proc(LPVOID lpParameter) { while (TRUE) { //等待线程的执行权,无限期等待 //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行 WaitForSingleObject(hMetex,INFINITE); if (g_nIndex++ < nMaxCnt) { cout << "Index = "<< g_nIndex << " "; cout << "Thread1 is ruuning" << endl; } else { break; } //资源使用完,释放互斥对象,互斥对象的线程ID变为0,互斥对象处于有信号状体 ReleaseMutex(hMetex); } return 0; } //新线程的起始地址 DWORD WINAPI Thread2Proc(LPVOID lpParameter) { while (TRUE) { //等待线程的执行权,无限期等待 //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行,否则不执行 WaitForSingleObject(hMetex,INFINITE); if (g_nIndex++ < nMaxCnt) { cout << "Index = " << g_nIndex << " "; cout << "Thread2 is ruuning" << endl; } else { break; } //资源使用完,释放互斥对象,互斥对象的线程ID变为0,互斥对象处于有信号状体 ReleaseMutex(hMetex); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { HANDLE hThread1 = NULL; HANDLE hThread2 = NULL; //创建互斥对象,主线程没有执行权 hMetex = CreateMutex(NULL, FALSE, NULL); //创建新的线程 hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL); hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL); //无须对新线程设置优先级等操作,关闭之 //良好的编码习惯 CloseHandle(hThread1); CloseHandle(hThread2); //主线程放弃执行全力 Sleep(5000); return 0; }运行结果:
实例2
如果在我们在创建互斥对象时,将CreateMetux的第二个对象设置为TURE,即hMetex = CreateMutex(NULL, TRUE, NULL);运行结果:
分析:
wMain主线程获得这个互斥对象的执行权,并且没有释放这个互斥对象,其他线程在WaitForSingleObject时,该互斥对象都属于未通知状态,因此Thread1和Thread2都不会执行;
实例3
在思路2的基础上,若我们在线程1和线程2的WaitForSingleObject函数前面,增加ReleaseMutex(hMutex)语句,执行情况和实例2一致;第一点说明:
对互斥对象来说,它是唯一与线程相关的内核对象;当主线程拥有互斥对象时,操作系统将互斥对象的线程ID设置为主线程的ID。
线程1在调用ReleaseMutex函数时,操作系统会判断当前线程的线程ID和互斥对象中的线程ID是否相等,只有相等才能正在释放;由于主线没有释放,互斥对象的线程ID是主线程的ID,导致其他线程没有执行的机会。对于互斥对象来说,必须是谁拥有谁完成释放;
实例4
对于如下的主线程函数来说,线程1和线程2想获得执行权,需要释放两次,代码如下:int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread1 = NULL;
HANDLE hThread2 = NULL;
//创建互斥对象,主线程有执行权
hMetex = CreateMutex(NULL, TRUE, NULL);
WaitForSingleObject(hMetex,INFINITE);
//创建新的线程
hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
//无须对新线程设置优先级等操作,关闭之
//良好的编码习惯
CloseHandle(hThread1);
CloseHandle(hThread2);
//CreateMutex创建时,主线程获得所有权,计数器+1
ReleaseMutex(hMetex);
//同一个线程内调用WaitForSingleObject,即此刻互斥对象处于未通知状态
//调用线程也会获得执行权,计数器再+1
ReleaseMutex(hMetex);
//主线程放弃执行全力
Sleep(5000);
return 0;
}
第二点说明:
我们在调用WaitForSingleObject函数时,若请求的线程ID和互斥对象内部的线程ID是相同的,即使此时此互斥对象处于未通知状态,该线程还可以请求到该回车对象的所有权,计数器则会再加1;所以实例4中需要释放互斥对象两次才行;
实例5:
第三点说明:当一个线程已经终止时(比如线程return),操作系统自动会帮助我们将互斥对象的线程ID设置为0,并且将引用计数器设置为0;
//新线程的起始地址
DWORD WINAPI Thread1Proc(LPVOID lpParameter)
{
//等待线程的执行权,无限期等待
//当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行
WaitForSingleObject(hMetex,INFINITE);
cout << "Thread1 is ruuning" << endl;
//线程结束,自动释放互斥变量,使其处于已通知状态
return 0;
}
//新线程的起始地址
DWORD WINAPI Thread2Proc(LPVOID lpParameter)
{
WaitForSingleObject(hMetex,INFINITE);
cout << "Thread2 is ruuning" << endl;
//线程结束,自动释放互斥变量,使其处于已通知状态
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread1 = NULL;
HANDLE hThread2 = NULL;
//创建互斥对象,主线程没有执行权
hMetex = CreateMutex(NULL, FALSE, NULL);
//创建新的线程
hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
//无须对新线程设置优先级等操作,关闭之
//良好的编码习惯
CloseHandle(hThread1);
CloseHandle(hThread2);
//主线程放弃执行权力
Sleep(5000);
return 0;
}
相关文章推荐
- CreateMutex利用互斥对象实现线程同步实例
- 利用互斥对象实现线程同步
- 利用互斥对象实现线程同步
- 利用互斥对象实现线程同步
- 利用互斥对象实现线程同步
- 利用互斥对象实现线程同步
- Ajax实例:实现查找员工和新建员工,利用XMLHttpRequest对象实现前后端交互
- 利用事件对象实现线程同步
- 利用事件对象实现线程同步
- windows下多线程同步(利用事件对象,互斥对象,关键代码段)实现
- 利用事件对象实现线程同步
- 利用"委托"实现对象实例按"多字段嵌套"排序
- 利用内核对象----互斥量实现应用程序只运行一个实例
- 互斥对象实现线程同步
- 利用事件对象 实现线程同步
- 利用事件对象实现线程同步
- 利用"委托"实现对象实例按"多字段嵌套"排序
- 多线程编程Demo[利用事件对象实现线程同步]
- CreateMutex利用互斥对象实现线程同步示例01
- 利用事件对象实现线程同步