VC++ 线程同步之信号量(Semaphore)
信号量与互斥量类似,唯一的区别是多个对象可持有信号量的所有权。假设有一个复杂的数学运算要用到整个逻辑
CPU 核。如果每个核只运行一个线程,计算结果没问题,但是,如果每个核运行多个线程,计算结果就有问题
了。另外,还假设计算过程中所需的线程数量比同时工作的逻辑核的数量多。
最好选择信号量对象来处理这种情况,我们可以把信号量对象的最大值设置为机器的逻辑核数量。当线程数量不超
过核的数量时,它们能同时工作,优化计算过程。当线程数量超过逻辑核的数量时,一些线程将被挂起,等待其他
线程执行完毕。
每次只有一个线程可以获得互斥量,与此不同的是,只要未超过可持有信号量所有权的最大数量,信号量仍然处于
触发状态。如果一个线程要等待信号量,那么在其他线程释放信号量之前它将被挂起。
信号量涉及的函数:
创建或打开一个现有的信号量对象,可以使用CreateSemaphore:
HANDLE WINAPI CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR szName
);
第 1个参数是安全特征,可以指定也可以空出不填(如果不填,该参数将被设为默认值)。第 2个参数没置初始数目以确定信号量的初始触发状态。如果设置为 0,信号量为未触发状态。这个值必须小于第3个参数lMaxmumCount 的值。lMaxmumCount 表示可同时持有信号量所有权的最多对象(线程)数目。必须提供信号量名(szName)。
使用 OpenSemaphore 也可以打开一个现有的信号量:
HANDLE WINAPI OpenSemaphore(
DWORD dwDesiredAccess,
BOOl bInheritHandle,
LPCTSTR szName
);
如果信号量不存在,该例程将失败并返回 NULL。线程操作完毕后,必须释放信号量以递减计数器计数,这样其他
线程才能获得信号量。用 ReleaseSemaphore API 可以释放信号量:
BOOL WINAPI ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
这个API有3个参数。第1个参数是之前 CreateSemaphore 或 OpenSemaphore 返回的信号量的句柄。第2个参数
是信号量要递减的对象数量,其值通常是 1,因为线程总是逐个被释放的。有一种情况例外线程 A 获得一个信号
量,然后要创建也需要信号量的线程 B。线程A 在线程B 创建好之前完成了自己的任务,线程B不知道它将被强制
终止,所以线程A要终止子线程且递减信号量为2。
代码示例
以下是使用 Win32API 信号量实现的线程同步代码的完整 C++ 实现。该示例使用两个线程,一个线程在计算 1 到 100 的总和,另一个线程在计算 101 到 200 的总和。在两个线程完成计算后,主线程将这两个部分的总和相加并打印结果。
#include <iostream>
#include <Windows.h>
using namespace std;
// 定义信号量和互斥量句柄
HANDLE sem1, sem2, mutex1, mutex2;
// 定义共享数据
int sum1, sum2;
// 计算 1 到 100 的总和
DWORD WINAPI Sum1(LPVOID lpParam) {
int i;
for (i = 1; i <= 100; i++) {
WaitForSingleObject(mutex1, INFINITE); // 获取互斥量
sum1 += i; // 计算部分总和
ReleaseSemaphore(sem1, 1, NULL); // 发送信号量
ReleaseMutex(mutex1); // 释放互斥量
}
return 0;
}
// 计算 101 到 200 的总和
DWORD WINAPI Sum2(LPVOID lpParam) {
int i;
for (i = 101; i <= 200; i++) {
WaitForSingleObject(mutex2, INFINITE);
sum2 += i;
ReleaseSemaphore(sem2, 1, NULL);
ReleaseMutex(mutex2);
}
return 0;
}
int main() {
DWORD threadID1, threadID2;
sem1 = CreateSemaphore(NULL, 0, 1, NULL);
sem2 = CreateSemaphore(NULL, 0, 1, NULL);
mutex1 = CreateMutex(NULL, false, NULL);
mutex2 = CreateMutex(NULL, false, NULL);
// 创建两个线程
HANDLE hThread1 = CreateThread(NULL, 0, Sum1, NULL, 0, &threadID1);
HANDLE hThread2 = CreateThread(NULL, 0, Sum2, NULL, 0, &threadID2);
// 等待信号量
WaitForSingleObject(sem1, INFINITE);
WaitForSingleObject(sem2, INFINITE);
// 计算总和
int sum = sum1 + sum2;
cout << "Sum = " << sum << endl;
// 释放所有句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
CloseHandle(sem1);
CloseHandle(sem2);
CloseHandle(mutex1);
CloseHandle(mutex2);
return 0;
}
代码实现中,分别创建了线程运行 Sum1
和 Sum2
函数,这两个函数分别计算 1 到 100 和 101 到 200 的部分总和。在每个线程的迭代中,获取互斥量,计算部分总和,释放信号量和释放互斥量。最后,主线程等待两个线程每个都释放了它的信号量时,将两个部分总和相加,并打印结果。 最后,释放所有句柄并返回 0。
该文章会更新,欢迎大家批评指正。
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
服务器课程:C++服务器