windows信号量使用

 信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。

下面结合图例3来演示信号量对象对资源的控制。在图3中,以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图(a)所示,最大资源计数和当前可用资源计数均为4,此后每增加一个对资源进行访问的线程(用黑色箭头表示)当前资源计数就会相应减1,图(b)即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时,将如图(c)所示,此时已达到最大资源计数,而当前可用资源计数也已减到0,其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后,将会释放出空间,图(d)已有两个线程退出对资源的占有,当前可用计数为2,可以再允许2个线程进入到对资源的处理。可以看出,信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。

  使用信号量内核对象进行线程同步主要会用到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);
}


上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为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);
}
<think>好的,用户想了解Windows信号量使用。首先,我需要回忆Windows API中信号量的相关知识。信号量是用于控制多线程或多进程对共享资源访问的同步机制。在Windows中,主要使用CreateSemaphore、OpenSemaphore、WaitForSingleObject、ReleaseSemaphore和CloseHandle这些函数。 接下来,我应该分步骤解释信号量的基本使用流程。可能需要先介绍创建信号量,然后说明如何等待和释放信号量,最后是关闭句柄。需要注意每个函数的参数,比如初始计数和最大计数,这些对用户来说很重要,容易出错的地方要重点说明。 然后,用户可能需要一个具体的例子来理解如何实际应用。因此,提供一个简单的多线程示例会比较合适,展示主线程创建信号量,工作线程获取和释放的过程。代码示例需要准确,确保语法正确,并且注释清楚每一步的作用。 同时,要提醒用户常见的注意事项,比如避免死锁,正确管理信号量计数,以及句柄泄漏的问题。这些是实际开发中容易遇到的坑,用户可能会忽略的地方。 另外,用户可能不太清楚信号量与互斥量、事件的区别,是否需要简要比较一下?不过问题主要集中在信号量使用,可能暂时不需要展开其他同步对象,除非用户后续提问。 需要确认所有术语翻译正确,比如“semaphore”翻译为“信号量”,函数名保持英文,因为实际编码中使用的是英文API。同时,确保使用中文回答,结构清晰,分点说明,便于用户逐步理解。 最后,检查是否有遗漏的关键点,比如跨进程使用时需要命名信号量,以及错误处理的方法。这些也是实际应用中重要的部分,应该包含在介绍中。</think>在Windows系统中,信号量(Semaphore)是用于多线程/多进程同步的核心对象之一。以下是分步说明和使用要点: --- ### 一、信号量的核心API ```c HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性(通常NULL) LONG lInitialCount, // 初始计数 LONG lMaximumCount, // 最大计数 LPCTSTR lpName // 命名信号量标识 ); HANDLE OpenSemaphore( DWORD dwDesiredAccess, // 访问权限(如SEMAPHORE_ALL_ACCESS) BOOL bInheritHandle, // 句柄继承属性 LPCTSTR lpName // 与CreateSemaphore一致的名称 ); DWORD WaitForSingleObject( HANDLE hHandle, // 信号量句柄 DWORD dwMilliseconds // 超时时间(INFINITE表示永久等待) ); BOOL ReleaseSemaphore( HANDLE hSemaphore, // 信号量句柄 LONG lReleaseCount, // 释放计数(通常为1) LPLONG lpPreviousCount // 接收原计数值的指针(可NULL) ); BOOL CloseHandle(HANDLE hObject); // 关闭句柄 ``` --- ### 二、典型使用流程 1. **创建信号量** ```c // 创建初始计数为2,最大为5的未命名信号量 HANDLE hSem = CreateSemaphore(NULL, 2, 5, NULL); ``` 2. **等待信号量** ```c DWORD dwWaitResult = WaitForSingleObject(hSem, INFINITE); if (dwWaitResult == WAIT_OBJECT_0) { // 成功获取信号量,当前计数减1 } ``` 3. **释放信号量** ```c if (!ReleaseSemaphore(hSem, 1, NULL)) { // 处理释放失败的错误 } ``` 4. **关闭句柄** ```c CloseHandle(hSem); ``` --- ### 三、跨进程同步示例 ```c // 进程A创建命名信号量 HANDLE hSemA = CreateSemaphore(NULL, 3, 5, TEXT("MySemaphore")); // 进程B打开同名信号量 HANDLE hSemB = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, TEXT("MySemaphore")); ``` --- ### 四、多线程示例代码 ```c #include <windows.h> #include <stdio.h> HANDLE hSemaphore; DWORD WINAPI ThreadProc(LPVOID lpParam) { WaitForSingleObject(hSemaphore, INFINITE); printf("线程%ld进入临界区\n", GetCurrentThreadId()); Sleep(2000); // 模拟操作耗时 ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } int main() { hSemaphore = CreateSemaphore(NULL, 3, 3, NULL); // 允许同时3个线程访问 HANDLE threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); } WaitForMultipleObjects(5, threads, TRUE, INFINITE); CloseHandle(hSemaphore); return 0; } ``` --- ### 五、关键注意事项 1. **计数管理** - 初始计数决定初始可用资源数 - 每次Wait减1,Release加1(不超过最大值) 2. **死锁防范** - 确保每个Wait都有对应的Release - 使用`WaitForMultipleObjects`时注意顺序 3. **错误处理** - 检查API返回值 - 使用`GetLastError()`获取错误代码 4. **命名规范** - 跨进程使用时名称需统一 - 遵循Windows对象命名规则 --- ### 六、信号量 vs 其他同步对象 | 对象类型 | 特点 | 适用场景 | |------------|-------------------------------|---------------------------| | 信号量 | 控制N个资源的访问 | 连接池、限流 | | 互斥量 | 严格所有权机制 | 排他性资源访问 | | 事件 | 状态触发机制 | 线程/进程间通知 | --- ### 常见问题解决方案 1. **信号量泄漏** - 使用`Process Explorer`工具检查句柄 - 确保每个Create都有对应的CloseHandle 2. **计数异常** - 添加调试日志输出当前计数 - 使用`ReleaseSemaphore`第三个参数获取原值 3. **跨会话同步** - 使用`Global\`前缀命名(需要提升权限) - 设置正确的安全描述符
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值