经典线程同步 互斥量Mutex的使用分析

       互斥量(mutex)内核对象用来确保一个线程独占对一个资源的访问。

       互斥量对象包含一个使用计数、线程ID以及一个递归计数。

       互斥量与关键段的行为完全相同。但是,互斥量是内核对象,而关键段是用户模式下的同步对象。

       线程ID用来标识当前占用这个互斥量的是系统中哪儿个线程,递归计数表示这个线程占用该互斥量的次数。

       他们一般用来对多个线程访问的同一块内存进行保护。如果多个线程要同时更新内存块,那么其中的数据将遭到破坏。互斥量可以确保正在访问内存块的任何线程会独占对内存块的访问权,这样就保证了数据的完整性。

 

       互斥量的使用规则:

1.如果线程ID为0 (无效线程ID),那么该互斥量不为任何线程所占用,它处于触发状态。

   可以理解为:无人使用,即触发。

2.如果线程ID为非零值,那么有一个线程已经占用了该互斥量,他处于未触发状态。

3.与所有其他内核对象不同,操作系统对互斥量进行了特殊处理,允许他们违反一些常规的规则。

 

 

       互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问。互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源。使用互斥量Mutex主要将用到四个函数。下面是这些函数的原型和使用说明。

 

第一个 CreateMutex

函数功能:创建互斥量(注意与事件Event的创建函数对比)

函数原型:

HANDLECreateMutex(

  LPSECURITY_ATTRIBUTES lpMutexAttributes,

  BOOL bInitialOwner,     

  LPCTSTR lpName

);

函数说明:

第一个参数表示安全控制,一般直接传入NULL。

第二个参数用来确定互斥量的初始拥有者。

如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。

如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。

第三个参数用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。

函数访问值:

成功返回一个表示互斥量的句柄,失败返回NULL。

 

第二个打开互斥量

函数原型:

HANDLEOpenMutex(

 DWORD dwDesiredAccess,

 BOOL bInheritHandle,

 LPCTSTR lpName     //名称

);

函数说明:

第一个参数表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS。详细解释可以查看MSDN文档。

第二个参数表示互斥量句柄继承性,一般传入TRUE即可。

第三个参数表示名称。某一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。

函数访问值:

成功返回一个表示互斥量的句柄,失败返回NULL。

 

第三个触发互斥量

函数原型:

BOOLReleaseMutex (HANDLE hMutex)

函数说明:

访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示自己已经结束访问,其它线程可以开始访问了。

 

最后一个清理互斥量

由于互斥量是内核对象,因此使用CloseHandle()就可以(这一点所有内核对象都一样)。

 

 

       在创建互斥量之后,为了获得对被保护资源的访问权,线程要调用一个等待函数并传入互斥量的句柄。在内部,等待函数会检查线程ID是否为0(互斥量处于触发状态)。如果为0,那么函数会把线程ID 设为调用线程的线程ID,把递归计数设为1,然后让调用线程继续运行。

       如果等待函数检测到线程ID不为0(互斥量处于未触发状态),那么调用线程将进入等待状态。当另一个线程将互斥量的线程ID设为0的时候,系统会记得有一个线程正在等待,于是它把线程ID设为正在等待的那个线程的ID,把递归计数设为1,使正在等待的线程变成可调度状态。这些对互斥量内核对象的检查和修改都是以原子方式进行的。

        即,等待函数检测的是 线程ID, 而不是递归计数。

 

在用来触发普通内核对象和撤销触发普通内核对象的规则中,有一条不适用于互斥量。

 

       假设线程正在等待一个未触发的互斥量对象,在这种情况下,线程通常会进入等待状态。但是,

系统会检查 想要获得互斥量的线程的线程ID 与 互斥量对象的内部记录的线程ID 是否相同。

       如果线程ID一致,那么系统会让线程保持可调度状态——即使该互斥量尚未触发。(线程所有权,可理解为同步时,无效)

       对于系统中的任何其他内核对象来说,我们都找不到这种“异常”的活动,

       每次线程成功的等待了一个互斥量,互斥量对象的递归计数会递增,使递归计数大于1的唯一途径就是利用这个异常,让线程多次等待同一个互斥量。

       当目前占有访问权的线程不再需要访问资源的时候,它必须调用releasemutex函数来释放互斥量。

       如果线程成功地等待了互斥量对象不止一次,需要调用ReleaseMutex 相同次数,才能使得对象的递归计数变成0。当递归计数变成0的时候,函数还会将线程ID设为0,这样就触发了对象。

 

以下代码中均忘记了关闭互斥量,大家写的时候不要忘记。

下面举例:

#include <iostream>
#include <windows.h>
#include <process.h>

using namespace std;

const int threadNum = 20;
HANDLE mutex;
volatile int number;

unsigned int __stdcall threadFunc(PVOID pm) {
	int th = * (int *)pm;
	WaitForSingleObject(mutex, INFINITE);
	number++;
	ReleaseMutex(mutex);
	return 0;
}

int main() {
	HANDLE handle[threadNum];
	mutex = CreateMutex(NULL, FALSE, NULL);
	int i,n=threadNum;
	while(n--) {
		number = 0;
		for(i=0; i<threadNum; i++) {
			handle[i] = (HANDLE) _beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
		}

		WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
		cout << "线程个数为" << number << endl;
	}

	getchar();
	return 0;
}

运行结果为:


 

mutex = CreateMutex(NULL, FALSE, NULL);

表示互斥量创建时,处于触发状态

若将FALSE 改为 TRUE,运行效果如下:


则没有任何显示,因为创建时 互斥量的线程ID是 主线程的线程ID, 递归计数为1,所以子线程处于等待状态。

 

下面例举不适用互斥量的情况:

一、

#include <iostream>
#include <windows.h>
#include <process.h>

using namespace std;

const int threadNum = 40;
HANDLE mutex;
volatile int number;

unsigned int __stdcall threadFunc(PVOID pm) {
	int th = * (int *)pm;
	Sleep(100);
	number++;
	Sleep(100);
	ReleaseMutex(mutex);
	return 0;
}

int main() {

	HANDLE handle[threadNum];
	mutex = CreateMutex(NULL, FALSE, NULL);

	int i,n=threadNum;

	while(n--) {
		number=0;
		for(i=0; i<threadNum; i++) {
				handle[i] = (HANDLE) _beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
				WaitForSingleObject(mutex, INFINITE);  // 等待函数检测线程ID
			}

			WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
			cout << "线程个数为" << number << endl;
	}
	getchar();
	return 0;
}

运行结果如下:


 

因为线程所有权的概念,此时子线程之间 不是互斥的关系了。

 

二、

#include <iostream>
#include <windows.h>
#include <process.h>

using namespace std;

const int threadNum = 10;
HANDLE mutex;
volatile int number;

unsigned int __stdcall threadFunc(PVOID pm) {
	int th = * (int *)pm;
	number++; //线程不安全
	return 0;
}

unsigned int __stdcall lastFunc(PVOID pm) {
	WaitForSingleObject(mutex, INFINITE);
	cout << "hello world" << endl;
	Sleep(100);
	ReleaseMutex(mutex);
	return 0;
}

int main() {
	HANDLE handle[threadNum],last_handle;
	mutex = CreateMutex(NULL, FALSE, NULL);
	int i,n=threadNum;

	for(i=0; i<threadNum; i++) {
		handle[i] = (HANDLE) _beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
		WaitForSingleObject(mutex, INFINITE);  // 等待函数检测线程ID
	}

	WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
	cout << "线程个数为" << number << endl;

	last_handle = (HANDLE) _beginthreadex(NULL, 0, lastFunc, NULL, 0, NULL);

	Sleep(1000);  //保证在回收mutex前 执行完last_handle,否则互斥量将不起作用
	CloseHandle(mutex);
	
	getchar();
	return 0;
}

运行效果如下:


由于互斥量未释放  导致last_handle线程,等待。

 

正确释放mutex

WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);之前,加入

注意,释放mutex必须在其占用的线程当中进行释放,在其他线程中释放将没有任何效果。

for(i=0; i<threadNum; i++) {
		ReleaseMutex(mutex);
	}

运行效果如下:


 

正常。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值