互斥内核对象
互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。
互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。
以互斥内核
对
象来保持
线
程同
步
可能用到的函数主要有
CreateMutex
()、
OpenMutex
()、
ReleaseMutex
()、
WaitForSingleObject
()和
WaitForMultipleObjects
()等。在使用互斥
对
象前,首先要通
过
CreateMutex
()或
OpenMutex
()
创
建或打
开
一个互斥
对
象。
CreateMutex
()函数原型
为
:
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指 针 BOOL bInitialOwner, // 初始 拥 有者 LPCTSTR lpName // 互斥 对 象名 ); |
参数
bInitialOwner
主要用来控制互斥
对
象的初始状
态
。一般多将其
设
置
为
FALSE
,以表明互斥
对
象在
创
建
时
并没有
为
任何
线
程所占有。如果在
创
建互斥
对
象
时
指定了
对
象名,那
么
可以在本
进
程其他地方或是在其他
进
程通
过
OpenMutex
()函数得到此互斥
对
象的句柄。
OpenMutex
()函数原型
为
:
HANDLE OpenMutex( DWORD dwDesiredAccess, // 访问标 志 BOOL bInheritHandle, // 继 承 标 志 LPCTSTR lpName // 互斥 对 象名 ); |
当目前 对资 源具有 访问权 的 线 程不再需要 访问 此 资 源而要离 开时 ,必 须 通 过 ReleaseMutex ()函数来 释 放其 拥 有的互斥 对 象,其函数原型 为 :
BOOL ReleaseMutex(HANDLE hMutex);
|
其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。
在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:
//
互斥
对
象
HANDLE hMutex = NULL;
char g_cArray[10];
UINT ThreadProc18(LPVOID pParam)
{
// 等待互斥 对 象通知
WaitForSingleObject(hMutex, INFINITE);
// 对 共享 资 源 进 行写入操作
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
// 释 放互斥 对 象
ReleaseMutex(hMutex);
return 0;
}
UINT ThreadProc19(LPVOID pParam)
{
// 等待互斥 对 象通知
WaitForSingleObject(hMutex, INFINITE);
// 对 共享 资 源 进 行写入操作
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
// 释 放互斥 对 象
ReleaseMutex(hMutex);
return 0;
}
……
void CSample08View::OnMutex()
{
// 创 建互斥 对 象
hMutex = CreateMutex(NULL, FALSE, NULL);
// 启 动线 程
AfxBeginThread(ThreadProc18, NULL);
AfxBeginThread(ThreadProc19, NULL);
// 等待 计 算完 毕
Sleep(300);
// 报 告 计 算 结 果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
HANDLE hMutex = NULL;
char g_cArray[10];
UINT ThreadProc18(LPVOID pParam)
{
// 等待互斥 对 象通知
WaitForSingleObject(hMutex, INFINITE);
// 对 共享 资 源 进 行写入操作
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
// 释 放互斥 对 象
ReleaseMutex(hMutex);
return 0;
}
UINT ThreadProc19(LPVOID pParam)
{
// 等待互斥 对 象通知
WaitForSingleObject(hMutex, INFINITE);
// 对 共享 资 源 进 行写入操作
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
// 释 放互斥 对 象
ReleaseMutex(hMutex);
return 0;
}
……
void CSample08View::OnMutex()
{
// 创 建互斥 对 象
hMutex = CreateMutex(NULL, FALSE, NULL);
// 启 动线 程
AfxBeginThread(ThreadProc18, NULL);
AfxBeginThread(ThreadProc19, NULL);
// 等待 计 算完 毕
Sleep(300);
// 报 告 计 算 结 果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
互斥
对
象在
MFC
中通
过
CMutex
类进
行表述。使用
CMutex
类
的方法非常
简单
,在构造
CMutex
类对
象的同
时
可以指明待
查询
的互斥
对
象的名字,在构造函数返回后即可
访问
此互斥
变
量。
CMutex
类
也是只含有构造函数
这
唯一的成
员
函数,当完成
对
互斥
对
象保
护资
源的
访问
后,可通
过调
用从父
类
CSyncObject
继
承的
UnLock
()函数完成
对
互斥
对
象的
释
放。
CMutex
类
构造函数原型
为
:
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
|
该类
的适用范
围
和
实现
原理与
API
方式
创
建的互斥内核
对
象是完全
类
似的,但要
简洁
的多,下面
给
出就是
对
前面的示例代
码经
CMutex
类
改写后的程序
实现
清
单
:
// MFC
互斥
类对
象
CMutex g_clsMutex(FALSE, NULL); UINT ThreadProc27(LPVOID pParam) { // 等待互斥 对 象通知 g_clsMutex.Lock(); // 对 共享 资 源 进 行写入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 释 放互斥 对 象 g_clsMutex.Unlock(); return 0; } UINT ThreadProc28(LPVOID pParam) { // 等待互斥 对 象通知 g_clsMutex.Lock(); // 对 共享 资 源 进 行写入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 释 放互斥 对 象 g_clsMutex.Unlock(); return 0; } …… void CSample08View::OnMutexMfc() { // 启 动线 程 AfxBeginThread(ThreadProc27, NULL); AfxBeginThread(ThreadProc28, NULL); // 等待 计 算完 毕 Sleep(300); // 报 告 计 算 结 果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); } |
信号量内核对象
信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
使用信号量内核
对
象
进
行
线
程同
步
主要会用到
CreateSemaphore
()、
OpenSemaphore
()、
ReleaseSemaphore
()、
WaitForSingleObject
()和
WaitForMultipleObjects
()等函数。其中,
CreateSemaphore
()用来
创
建一个信号量内核
对
象,其函数原型
为
:
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指 针 LONG lInitialCount, // 初始 计 数 LONG lMaximumCount, // 最大 计 数 LPCTSTR lpName // 对 象名指 针 ); |
参数 lMaximumCount 是一个有符号 32 位 值 ,定 义 了允 许 的最大 资 源 计 数,最大取 值 不能超 过 4294967295 。 lpName 参数可以 为创 建的信号量定 义 一个名字,由于其 创 建的是一个内核 对 象,因此在其他 进 程中可以通 过该 名字而得到此信号量。 OpenSemaphore ()函数即可用来根据信号量名打 开 在其他 进 程中 创 建的信号量,函数原型如下:
HANDLE OpenSemaphore( DWORD dwDesiredAccess, // 访问标 志 BOOL bInheritHandle, // 继 承 标 志 LPCTSTR lpName // 信号量名 ); |
在
线
程离
开对
共享
资
源的
处
理
时
,必
须
通
过
ReleaseSemaphore
()来增加当前可用
资
源
计
数。否
则
将会出
现
当前正在
处
理共享
资
源的
实际线
程数并没有达到要限制的数
值
,而其他
线
程却因
为
当前可用
资
源
计
数
为
0
而仍无法
进
入的情况。
ReleaseSemaphore
()的函数原型
为
:
BOOL ReleaseSemaphore( HANDLE hSemaphore, // 信号量句柄 LONG lReleaseCount, // 计 数 递 增数量 LPLONG lpPreviousCount // 先前 计 数 ); |
该 函数将 lReleaseCount 中的 值 添加 给 信号量的当前 资 源 计 数,一般将 lReleaseCount 设 置 为 1 ,如果需要也可以 设 置其他的 值 。 WaitForSingleObject ()和 WaitForMultipleObjects ()主要用在 试图进 入共享 资 源的 线 程函数入口 处 ,主要用来判断信号量的当前可用 资 源 计 数是否允 许 本 线 程的 进 入。 只有在当前可用 资 源 计 数 值 大于 0 时 ,被 监视 的信号量内核 对 象才会得到通知。
信号量的使用特点使其更适用于 对 Socket (套接字)程序中 线 程 的同 步 。例如,网 络 上的 HTTP 服 务 器要 对 同一 时间 内 访问 同一 页 面的用 户 数加以限制, 这时 可以 为 没一个用 户对 服 务 器的 页 面 请 求 设 置一个 线 程,而 页 面 则 是待保 护 的共享 资 源,通 过 使用信号量 对线 程的同 步 作用可以确保在任一 时 刻无 论 有多少用 户对 某一 页 面 进 行 访问 ,只有不大于 设 定的最大用 户 数目的 线 程能 够进 行 访问 ,而其他的 访问 企 图则 被挂起,只有在有用 户 退出 对 此 页 面的 访问 后才有可能 进 入。下面 给 出的示例代 码 即展示了 类 似的 处 理 过 程 :
//
信号量
对
象句柄
HANDLE hSemaphore;
UINT ThreadProc15(LPVOID pParam)
{
// 试图进 入信号量 关 口
WaitForSingleObject(hSemaphore, INFINITE);
// 线 程任 务处 理
AfxMessageBox(" 线 程一正在 执 行 !");
// 释 放信号量 计 数
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
// 试图进 入信号量 关 口
WaitForSingleObject(hSemaphore, INFINITE);
// 线 程任 务处 理
AfxMessageBox(" 线 程二正在 执 行 !");
// 释 放信号量 计 数
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
// 试图进 入信号量 关 口
WaitForSingleObject(hSemaphore, INFINITE);
// 线 程任 务处 理
AfxMessageBox(" 线 程三正在 执 行 !");
// 释 放信号量 计 数
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
……
void CSample08View::OnSemaphore()
{
// 创 建信号量 对 象
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
// 开 启 线 程
AfxBeginThread(ThreadProc15, NULL);
AfxBeginThread(ThreadProc16, NULL);
AfxBeginThread(ThreadProc17, NULL);
}
HANDLE hSemaphore;
UINT ThreadProc15(LPVOID pParam)
{
// 试图进 入信号量 关 口
WaitForSingleObject(hSemaphore, INFINITE);
// 线 程任 务处 理
AfxMessageBox(" 线 程一正在 执 行 !");
// 释 放信号量 计 数
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
// 试图进 入信号量 关 口
WaitForSingleObject(hSemaphore, INFINITE);
// 线 程任 务处 理
AfxMessageBox(" 线 程二正在 执 行 !");
// 释 放信号量 计 数
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
// 试图进 入信号量 关 口
WaitForSingleObject(hSemaphore, INFINITE);
// 线 程任 务处 理
AfxMessageBox(" 线 程三正在 执 行 !");
// 释 放信号量 计 数
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
……
void CSample08View::OnSemaphore()
{
// 创 建信号量 对 象
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
// 开 启 线 程
AfxBeginThread(ThreadProc15, NULL);
AfxBeginThread(ThreadProc16, NULL);
AfxBeginThread(ThreadProc17, NULL);
}
上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于hSemaphore的当前可用资源计数分别为2和1,此时的hSemaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,hSemaphore的当前资源计数减少到0,hSemaphore将不再得到通知,WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出,信号量始终保持了同一时刻不超过2个线程的进入。
在
MFC
中,通
过
CSemaphore
类对
信号量作了表述。
该类
只具有一个构造函数,可以构造一个信号量
对
象,并
对
初始
资
源
计
数、最大
资
源
计
数、
对
象名和安全属性等
进
行初始化,其原型如下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
|
在构造了 CSemaphore 类对 象后,任何一个 访问 受保 护 共享 资 源的 线 程都必 须 通 过 CSemaphore 从父 类 CSyncObject 类继 承得到的 Lock ()和 UnLock ()成 员 函数来 访问 或 释 放 CSemaphore 对 象。与前面介 绍 的几 种 通 过 MFC 类 保持 线 程同 步 的方法 类 似,通 过 CSemaphore 类 也可以将前面的 线 程同 步 代 码进 行改写, 这 两 种 使用信号量的 线 程同 步 方法无 论 是在 实现 原理 上 还 是从 实现结 果上都是完全一致的。下面 给 出 经 MFC 改写后的信号量 线 程同 步 代 码 :
// MFC
信号量
类对
象
CSemaphore g_clsSemaphore(2, 2); UINT ThreadProc24(LPVOID pParam) { // 试图进 入信号量 关 口 g_clsSemaphore.Lock(); // 线 程任 务处 理 AfxMessageBox(" 线 程一正在 执 行 !"); // 释 放信号量 计 数 g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc25(LPVOID pParam) { // 试图进 入信号量 关 口 g_clsSemaphore.Lock(); // 线 程任 务处 理 AfxMessageBox(" 线 程二正在 执 行 !"); // 释 放信号量 计 数 g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc26(LPVOID pParam) { // 试图进 入信号量 关 口 g_clsSemaphore.Lock(); // 线 程任 务处 理 AfxMessageBox(" 线 程三正在 执 行 !"); // 释 放信号量 计 数 g_clsSemaphore.Unlock(); return 0; } …… void CSample08View::OnSemaphoreMfc() { // 开 启 线 程 AfxBeginThread(ThreadProc24, NULL); AfxBeginThread(ThreadProc25, NULL); AfxBeginThread(ThreadProc26, NULL); } |