`

经典线程同步 互斥量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);
	}

运行效果如下:


 

正常。

 

 

  • 大小: 19.6 KB
  • 大小: 14 KB
  • 大小: 20.4 KB
  • 大小: 9.5 KB
  • 大小: 9.6 KB
0
0
分享到:
评论

相关推荐

    Linux线程同步之互斥量(mutex).pdf

    本文将详细介绍Linux环境下,如何使用POSIX线程库中的互斥量API进行线程同步。 互斥量是用于多线程编程的一种锁定机制,其基本功能是通过锁定某个资源,在一段时间内只能有一个线程对其进行操作。当一个线程拥有...

    C# 多线程同步与互斥,使用Mutex和AutoResetEvent类

    在C#编程中,多线程同步和互斥是并发编程中的关键概念,它们用于确保在多线程环境中数据的一致性和程序的正确性。Mutex(互斥锁)和AutoResetEvent(自动重置事件)是.NET框架提供的两种工具,用于解决这些问题。 ...

    linux上实现多进程和多线程实现同步互斥(源代码)

    总结,Linux上的多进程和多线程编程涉及复杂的同步互斥操作,开发者需要理解各种同步机制,根据具体需求选择合适的方法。通过阅读和学习提供的源代码,可以加深对这些概念的理解,提升实际开发能力。

    Linux线程同步之互斥量(mutex)[收集].pdf

    互斥量,或互斥锁,是实现线程同步的一种基础工具,它确保同一时间只有一个线程能访问受保护的临界区。在Linux中,互斥量遵循POSIX线程标准,并通过`pthread`库来实现。 互斥量的类型为`pthread_mutex_t`,在`...

    多线程编程和操作系统线程同步互斥演示

    在这个“多线程编程和操作系统线程同步互斥演示”中,作者可能创建了一个或多个人工场景,展示了如何在VC++环境中使用多线程,并且演示了线程同步和互斥的实践应用。这可能涉及到以下几个方面: 1. **线程创建**:...

    linux上互斥线程Mutex的代码及解释

    在多线程编程中,互斥量(Mutex)是一种常见的同步机制,用于防止多个线程同时访问共享资源,从而避免数据竞争和不一致状态的发生。本文将深入探讨Linux环境下C++互斥线程Mutex的使用方法,通过解析示例代码,帮助...

    线程同步机制代码,用c++写的,:使用Windows互斥信号量操作函数和同步机制的Peterson,实现进程互斥和同步

    小实验三:根据同步机制的Peterson软件解决方案尝试自己编程实现线程同步机制和用于上述线程并发问题的解决,并基于程序运行时间长短将其与基于Windows互斥信号量的线程同步机制的效率展开比较。 实验要求:线程主体...

    Mutex 互斥量使用实例

    互斥量(Mutex)是Windows操作系统中的一个同步对象,用于控制多个线程对共享资源的访问,确保在任何时刻只有一个线程能访问该资源。在多线程编程中,互斥量是保证数据一致性、避免数据竞争的重要工具。本实例将深入...

    vc++ multithread多线程教程---线程通信--利用事件对象,线程同步--使用信号量,线程同步--使用互斥量,线程同步--使用临界区

    互斥量(Mutex)是另一种常见的线程同步工具,确保同一时间只有一个线程能访问特定的资源。它是一个二态同步对象,要么被持有,要么未被持有。当一个线程试图获得已持有的互斥量时,该线程会被挂起,直到拥有互斥量...

    线程同步的四种详细使用方法--临界区、互斥量、事件等

    线程同步的四种详细使用方法--临界区、互斥量、事件等 线程同步是计算机软件开发的重要技术,多线程同步技术的原理和实现对软件开发人员来说非常重要。本文对多线程的各种同步技术的原理和实现进行了初步探讨,包括...

    实例讲述线程的同步互斥

    综上所述,线程同步互斥对于构建稳定、高效的多线程程序至关重要。通过合理使用各种同步机制,我们可以确保程序在并发环境下的正确性,避免数据竞争和死锁等问题。在学习和实践中,理解并熟练掌握这些概念和工具,将...

    利用Mutex互斥变量实现线程同步机制

    本主题将深入探讨如何利用Mutex互斥变量来实现线程同步,并通过实际案例分析其在循环计数中的应用。 首先,理解Mutex的基本概念。Mutex,全称为“互斥量”,它是一种同步原语,用于保护共享资源。当一个线程获取了...

    操作系统线程同步算法

    本主题将深入探讨未使用和使用Windows互斥量的线程同步方案,以及Peterson算法这一经典的软件解决方案。 首先,未使用Windows互斥量的线程同步可能导致线程间的不协调。在多线程环境中,如果没有适当的同步机制,...

    mutex在线程的使用

    在C++中,`mutex`(互斥量)是实现线程同步的一种基本工具,它允许只让一个线程访问共享资源,从而避免了竞态条件的发生。在VS2008中,我们可以利用C++标准模板库(STL)中的`std::mutex`类来实现这一功能。 首先,...

    Linux线程同步之互斥量(mutex)

    互斥量(也称为互斥锁)出自POSIX线程标准,可以用来同步同一进程中的各个线程。当然如果一个互斥量存放在多个进程共享的某个内存区中,那么还可以通过互斥量来进行进程间的同步。  互斥量,从字面上可以知道是...

    四种线程同步互斥的控制方法.pdf

    为了确保对共享资源的有序访问和防止数据竞争,有四种主要的线程同步互斥的控制方法,分别是互斥量(Mutex)、临界区(Critical Section)、信号量(Semaphore)和事件(Event)。下面将详细介绍这四种方法的原理和...

    VC++6.0下利用互斥量同步线程来实现文件读取进度条C++源代码程序小实例

    5. **线程同步**:在多线程环境下,可以使用互斥量来同步读取文件的线程和更新进度条的线程。当读取线程读取一定比例的数据后,它释放锁,让更新线程可以修改进度条。更新完成后,更新线程再释放锁,读取线程继续...

    VisualC++线程同步技术剖析临界区,时间,信号量,互斥量[定义].pdf

    在 Visual C++ 中,线程同步技术的实现可以通过使用临界区、事件、信号量、互斥量等内核对象来实现。这些内核对象可以单独使用,也可以组合使用以实现更加复杂的同步机制。 在多线程编程中,线程同步技术的应用非常...

Global site tag (gtag.js) - Google Analytics