目录
- 前言
- 环境
- 简介
- 相关函数
- CreateMutex
- Wait 函数
- ReleaseMutex
- CloseHandle
- 其他
- 互斥锁的名字
- 未命名互斥锁的同步
- 互斥锁的意外终止
- 临界区对象
- 参考
前言
- 本文所涉及的同步主要描述在 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
错误
更多关于内核对象命名空间的知识可以阅读参考中的链接
未命名互斥锁的同步
- 通过线程间(或进程间)复制句柄或者父子句柄继承实现,这里我们主要讲一下复制句柄
- 复制句柄:通过该方法可以在两个进程之间传递句柄,但相比于命名句柄和父子继承的方式,这种方式显然更加复杂,它需要在创建句柄的进程和使用句柄的进程间进行通信(如,命名管道,命名共享内存)。另一个需要注意的地方,复制的句柄本质上和源句柄是等同的,你可以理解为指针间的赋值,赋值过后的两个指针实际是指向的相同的区域,任何改变都会影响两个指针指向的区域,句柄也是如此
- 语法
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 提供的一种用户模式下的线程同步机制,不完全等同于操作系统中的临界区这个概念
参考
- Synchronization
- 《Windows 核心编程》
- Process Security and Access Rights