前言
- 本文所涉及的同步主要描述在 Windows 环境下的机制,和 Linux 中的同步机制有一定的联系,但注意并不完全相同。类似于,Windows 和 Linux 按照自己的方式实现了操作系统中同步机制的概念
- 本文记录的是 Windows 下的互斥锁同步机制,但在 Windows 的同步机制中,其中很多的概念和逻辑同样适用于事件(Event),信号量,计时器等其他同步机制
环境
- OS:win
- IDE:Visual Studio 2015
简介
-
简介:互斥锁是一种同步对象,当没有任何线程拥有互斥锁时,互斥锁处于有信号(signaled)状态,当互斥锁被某个线程拥有,则它处于无信号状态(nonsignaled)。
顾名思义,互斥锁就是一种为了达到访问共享资源而互斥目的的锁。比如生活中,公共厕所就是一种共享资源,公厕一次只能有一个人使用,使用者在使用的时候就会关门上锁,使用完之后需要开门释放锁。对于每个使用者来说,这个锁一次只能被一个人占有
-
特点
- 任何一个互斥锁,一次只能被一个线程拥有
- 可以跨进程使用,即进程间同步
-
适用场景:同步一些共享资源,比如共享内存(shared memory)
相关函数
CreateMutex
- 作用:创建或打开命名或未命名的互斥锁对象。如果某互斥锁已经被创建,当再次使用
CreateMutex
操做该互斥锁,实际的操作等效于OpenMutex
,但通过GetLastError
会返回ERROR_ALREADY_EXISTS
标识 - 语法
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
[in] BOOL bInitialOwner,
[in, optional] LPCSTR lpName
);
- 参数
- lpMutexAttributes,为 NULL 时,句柄不能被子进程继承
- bInitialOwner,为 true 时,创建该互斥锁的线程获取该互斥锁
- lpName,互斥锁的名字,为 NULL 时,为未命名互斥锁,关于未命名互斥锁如何传递见下方“未命名互斥锁的同步”
Wait 函数
-
Wait 函数是一系列提供类似功能的等待函数(如
WaitForMultipleObjects
),该函数的作用是请求某个互斥锁的使用权,若没有获取到,则阻塞 - 等待函数的返回值表明等待函数因为某些原因返回,而不是正常的互斥锁信号转换
- 多个线程等待互斥锁时,只有一个线程会被随机选择获取互斥锁
ReleaseMutex
- 作用:释放控制权,释放后,互斥锁变为有信号状态
- 语法
BOOL ReleaseMutex(
[in] HANDLE hMutex
);
- 参数:hMutex 是要释放的互斥锁句柄
CloseHandle
- 作用:关闭句柄,本文中即关闭互斥锁
【注】除了使用CloseHandle
手动关闭句柄外,当某个进程终止后,也会自动关闭句柄
【注】CloseHandle
关闭当前线程中使用的句柄,但如果还有其他线程拥有该句柄,那么句柄对象并未真正关闭,只有当最后一个该对象被关闭,句柄才会真正关闭
如图,只有当所有句柄均被关闭,互斥锁对象才会自动关闭
其他
互斥锁的名字
特别注意的是,互斥锁的名字和其他同步对象(如,事件,信号量)的名字位于相同的命名空间,因此如果互斥锁有名字为“ExampleName”,事件也有名字为“ExampleName”则发生冲突,通过 GetLastError
函数返回 ERROR_INVALID_HANDLE
错误。
更多关于内核对象命名空间的知识可以阅读参考中的链接。
未命名互斥锁的同步
-
命名的互斥锁我们可以很容易理解如何让两个或多个线程使用相同的互斥锁。而未命名的互斥锁要如何让系统中多个线程与同一个互斥锁产生联系呢,答案是通过线程间(或进程间)复制句柄或者父子句柄继承实现,这里我们主要讲一下复制句柄
-
复制句柄:通过该方法可以在两个进程之间传递句柄,但相比于命名句柄和父子继承的方式,这种方式是最麻烦的,它需要在创建句柄的进程和使用句柄的进程间进行通信(如,命名管道,命名共享内存),当然这一步 Windows 通过高层函数隐藏了底层实现的细节,也就是说,你只需要调用一个
DuplicateHandle
函数即可完成两个进程的通信。另一个需要注意的地方,复制的句柄本质上和源句柄是等同的,你可以理解为指针间的赋值,赋值过后的两个指针实际是指向的相同的区域,任何改变都会影响两个指针指向的区域,句柄也是如此。
-
语法
BOOL DuplicateHandle(
[in] HANDLE hSourceProcessHandle,
[in] HANDLE hSourceHandle,
[in] HANDLE hTargetProcessHandle,
[out] LPHANDLE lpTargetHandle,
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwOptions
);
- 参数
- hSourceProcessHandle:源进程的句柄,该进程必须有
PROCESS_DUP_HANDLE
的接入权限。源句柄可以通过 GetCurrentProcess 获得 - hSourceHandle:要被复制的句柄,比如互斥锁句柄
- hTargetProcessHandle:目标进程的句柄,该进程必须有
PROCESS_DUP_HANDLE
的接入权限。目标句柄可以通过 OpenProcess 获得 - lpTargetHandle:注意它是一个指针,指向复制过来的句柄,“LP” 是 long pointer 的缩写
- dwDesiredAccess:新句柄的权限设置。一般通过复制得到的句柄的权限范围 ≤ 原句柄
- bInheritHandle:该句柄能否被继承
- dwOptions:一些可选的配置项,这里不展开
- hSourceProcessHandle:源进程的句柄,该进程必须有
关于句柄复制并非本篇的重点,详细内容移步官方文档
互斥锁的意外终止
比如,当前拥有互斥锁的线程终止,而该线程并未释放互斥锁,此时互斥锁被标记为遗弃(abandoned),它表明互斥锁未被正确释放。
其他等待该互斥锁的线程可以获取它,但对应的 wait
函数会返回 WAIT_ABANDONED
来表明互斥锁对象被遗弃,由此表明此时被互斥锁操控的共享资源处于未定义状态(undefined state)
临界区对象
临界区(critical section)对象提供类似与互斥锁的功能,区别在于临界区对象不提供进程间同步,只能提供同一进程中的线程的同步
【注】这里的临界区对象是 Windows 提供的一种用户模式下的线程同步机制,不完全等同于操作系统中的临界区这个概念
示例
在本示例中,我们启动两个进程,分别为 A process 和 B process。
A process 产生一个互斥锁,名为 MutexDemo,并且在产生时就获得该锁的使用权,之后就是执行使用共享资源的代码,然后释放互斥锁,最终关闭互斥锁句柄。
B process 打开名为 MutexDemo,获得 MutexDemo 的使用权后,执行使用共享资源的代码,然后释放互斥锁,最终关闭互斥锁句柄。
A process cpp文章来源:https://www.toymoban.com/news/detail-462221.html
void MutexSynchronize()
{
cout << "A thread: enter mutex creator" << endl;
// create a mutex and initially get it
HANDLE hMutex = CreateMutex(NULL, true, _TEXT("MutexDemo"));
// Simulate execute business logic
cout << "A thread: set data into shared memory" << endl;
// release mutex, mutex becomes signaled
ReleaseMutex(hMutex);
// close handle
Sleep(5000); // wait B thread get mutex, because we cannot predict the execution order
// of multiple processes
CloseHandle(hMutex);
}
B process cpp文章来源地址https://www.toymoban.com/news/detail-462221.html
void MutexSynchronize()
{
cout << "B thread: enter mutex opener" << endl;
HANDLE hMutex = NULL;
// wait A thread create mutex and open it
while (hMutex == NULL)
{
hMutex = OpenMutex(MUTEX_ALL_ACCESS, false, _TEXT("MutexDemo"));
cout << "B thread: wait mutex" << endl;
}
// wait signaled mutex
WaitForSingleObject(hMutex, INFINITE);
// Simulate execute business logic
cout << "B thread: get data from shared memory" << endl;
// release mutex, mutex becomes signaled
ReleaseMutex(hMutex);
// close handle
CloseHandle(hMutex);
}
参考
- Synchronization
- 《Windows 核心编程》
- Process Security and Access Rights
- Thread Handles and Identifiers:what is pseudo handle
到了这里,关于【学习笔记】Windows 下线程同步之互斥锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!