我们知道进程间的同步操作都是要借助内核来完成的,和同一个进程中的线程同步只需要在用户模式下是有很大差别的,当然,对于进程安全的,对于线程肯定也是安全的,但在用户模式下的线程同步所需消耗的代价相对于通过内核完成的同步是很小的。所以不要利用进程同步的方式来进行同一进程中线程的同步。
这里先讨论进程同步的方式之一:互斥量(Mutex)。
互斥量内核对象能够确保一个进程独占对一个资源的访问。互斥量与关键段(线程同步方式)的行为完全相同,当互斥量是内核对象,而关键段是用户模式下的的同步对象。
互斥量对象包含:一个线程ID,使用计数和递归计数。线程ID表示当前占用该互斥量的线程ID,递归计数表示该线程占用互斥量的次数,使用计数表示使用互斥量对象的不同线程的个数。
互斥量对象有许多用途,它是使用最为频繁的内核对象之一,一般用来对多个进程访问同一块内存进行同步。
互斥量的使用如下:
(1)CreateMutex()
HANDLE WINAPI CreateMutex(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner,
_In_opt_ LPCTSTR lpName
);
lpMutexAttributes:互斥量安全访问属性,一般置为NULL;
bInitialOwner:控制互斥量的初始状态,一般设置为false,是互斥量的线程ID和递归计数都设为0,表示互斥量不被任何进程占用,处于触发状态,其他进程可以进行调用等待函数,获得该互斥量对象,获得对同步资源的占用。如果初始值设为true,互斥量的线程ID设置为当前线程,递归计数设为1,表示当前线程占用互斥量对象,拥有对同步资源的独占,互斥量处于未触发状态。
lpName:用于创建有名的内核对象,即用来创建跨进程边界的内核对象。
CreateMutex用于创建名为lpName的互斥量内核对象,并返回指向该内核对象的句柄。如果该内核对象已经存在,那么CreateMutex会返回该内核对象的句柄,并通过系统返回错误ERROR_ALREADY_EXISTS,通过GetLastError()获得。
(2)OpenMutex()
HANDLE WINAPI OpenMutex(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCTSTR lpName
);
dwDesiredAccess:对互斥量对象访问权限的设置,MUTEX_ALL_ACCESS 请求对互斥体的完全访问,MUTEX_MODIFY_STATE 允许使用 ReleaseMutex 函数,SYNCHRONIZE 允许互斥体对象同步使用;
bInheritHandle:是否希望子进程继承互斥量对象的句柄,一般设置为false;
lpName:要打开的互斥量对象的名称;
OpenMutex用于打开已经存在的互斥量对象,若打开成功,则返回指向该内核对象的句柄,否则返回NULL。可以使用CreateMutex来实现打开功能。
(3)WaitForSingleObject()
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds
);
hHandle:指向内核对象的句柄;
dwMilliseconds:线程最大等待多长时间,直到该对象被触发。经常使用INFINITE,表示阻塞等待。
WaitForSingleObject被称呼为等待函数,是等待内核对象被触发通用的等待函数,被用在所有的内核对象触发等待中。等待函数在等待互斥量内核对象时,会判断互斥量的线程ID是否为0,如果为非0,表示互斥量处于未触发状态,等待函数会被阻塞。当另外一个线程将互斥量释放,使其线程ID为0时,系统会唤醒阻塞的等待函数,把互斥量的线程ID设置为它的线程ID,使其成为可调度状态。
还有一个WaitForMultipleObject函数,用于等待多个内核对象被触发。
(4)ReleaseMutex()
BOOL WINAPI ReleaseMutex(
_In_ HANDLE hMutex
);
hMutex:互斥量内核对象的句柄;
当一个线程访问完通过互斥量对象获得的独占资源后,应该调用ReleaseMutex,使互斥量恢复为未触发状态。即设置互斥量对象的线程ID和递归计数为0,当递归计数大于1时,还有进行对应多次的ReleaseMutex。
(5)CloseHandle()
BOOL WINAPI CloseHandle(
_In_ HANDLE hObject
);
hObject:指向内核对象的句柄
无论以什么方式创建内核对象,我们都必须通过调用CloseHandle向系统表明结束使用内核对象。如果传入的句柄有效,系统将获得内核对象数据结构的地址,并将结构中的使用计数减1,如果使用计数0,就会将内核对象销毁,从内存空间中擦除。