Win32 - 进程间通信(IPC)信号量
信号量(Semaphore)是一个非常重要的同步机制,用于控制多个线程或进程对共享资源的访问。其基本思想是使用一个整数变量来表示可用资源的数量,通过对这个整数的操作来控制资源的分配,从而实现对竞态条件的管理。信号量的操作是原子的,即在一个操作进行的过程中,不会被其他进程或线程中断 。
信号量主要有两种类型:
- 二进制信号量(或互斥锁):它的值只能是0或1,用来实现互斥,即在同一时刻只允许一个线程访问共享资源。
- 计数信号量:可以取一个整数值,表示可用资源的数量。这允许多个线程同时访问同一资源,但总数不超过最大资源数。
主要操作:信号量有两个基本操作,通常被称为P(等待)操作和V(释放)操作。
P操作(等待):
- 当线程想要访问资源时,它会执行P操作。
- 如果信号量的值大于0,表示有资源可用,信号量的值将减一,线程继续执行。
- 如果信号量的值为0,线程将进入阻塞状态,等待信号量值大于0。
V操作(释放):
- 当线程释放资源时,执行V操作。
- 信号量的值加一。
- 如果有其他线程因信号量的值为0而阻塞,这些线程中的一个将被唤醒。
信号量在线程和进程中的区别
信号量这一概念可以应用在线程通信和进程通信上,基本的操作是一样的,都是PV操作,但在具体的实现和使用场景上还是有很大的区别的:
线程间的信号量
- 存储位置:线程间使用的信号量通常存储在同一进程的内存空间,因为线程共享同一进程的地址空间。
- 资源共享:线程信号量主要用于同一个应用程序内部的多个线程之间的同步,因为所有线程可以直接访问到相同的内存变量和数据结构。
- 实现方式:在多线程环境中,可以使用较轻量级的同步机制,例如C++11提供的std::mutex和std::condition_variable等。
进程间的信号量
- 存储位置:进程间信号量必须存储在所有相关进程都可以访问到的位置,通常是操作系统的内核空间。这样做是为了保证数据的一致性和安全性。
- 资源共享:进程间信号量用于不同进程之间的同步,这些进程可能运行相同或不同的应用程序代码,它们彼此之间不共享内存空间。
- 实现方式:需要操作系统提供的特定API支持,例如Windows的CreateSemaphore和ReleaseSemaphore。
// 功能:创建或打开一个命名或无名的信号量对象。
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCWSTR lpName
)
// 功能:等待信号量对象变为可用(即信号量的计数大于0),该函数就是信号量的P操作。
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
)
// 功能:释放信号量对象,增加信号量的计数,相当于信号量的V操作。
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
)
// 功能:关闭一个打开的对象句柄,例如信号量句柄。
BOOL CloseHandle( HANDLE hObject )
示例代码:
-
SemaphoreWait.exe:进程会阻塞等待信号量(P 操作)。 -
SemaphoreSignal.exe:释放一个信号量(V 操作)。
两者通过相同的信号量名称 Global\TestSemaphore 进行通信。
文件1:SemaphoreWait.cpp(等待/阻塞进程)
#include <windows.h>
#include <stdio.h>
#define SEM_NAME L"Global\\TestSemaphore"
int main()
{
// 打开或创建命名信号量(初始计数 = 0,最大计数 = 1)
HANDLE hSem = CreateSemaphoreW(NULL, 0, 1, SEM_NAME);
if (hSem == NULL)
{
printf("CreateSemaphore 失败,错误码: %d\n", GetLastError());
return 1;
}
printf("[等待进程 PID=%d] 正在等待信号量...\n", GetCurrentProcessId());
// P 操作:如果信号量计数为 0,进程会在此处阻塞,直到计数 > 0
DWORD ret = WaitForSingleObject(hSem, INFINITE);
if (ret == WAIT_OBJECT_0)
{
printf("[等待进程] 获得信号量,进入临界区\n");
printf("[等待进程] 模拟工作 2 秒...\n");
Sleep(2000);
printf("[等待进程] 工作完成,释放信号量并退出。\n");
// 实际使用中,离开临界区时应该释放信号量,否则资源会一直被占用。
// 这里我们主动释放,以便后续其他等待进程可以继续。
ReleaseSemaphore(hSem, 1, NULL);
}
else
{
printf("等待失败,错误码: %d\n", GetLastError());
}
CloseHandle(hSem);
return 0;
}
文件2:SemaphoreSignal.cpp(释放/唤醒进程)
#include <windows.h>
#include <stdio.h>
#define SEM_NAME L"Global\\TestSemaphore"
int main()
{
// 打开已有的信号量(如果不存在则创建,但通常应先运行等待进程以创建)
HANDLE hSem = CreateSemaphoreW(NULL, 0, 1, SEM_NAME);
if (hSem == NULL)
{
printf("打开信号量失败,错误码: %d\n", GetLastError());
return 1;
}
printf("[释放进程 PID=%d] 准备释放信号量,按回车键执行 V 操作...\n", GetCurrentProcessId());
getchar();
// V 操作:信号量计数加 1,如果有等待的进程,其中一个会被唤醒
if (ReleaseSemaphore(hSem, 1, NULL))
{
printf("[释放进程] 信号量已释放(计数+1)。如果有等待的进程,它现在会被唤醒。\n");
}
else
{
printf("释放信号量失败,错误码: %d\n", GetLastError());
}
CloseHandle(hSem);
return 0;
}
运行演示步骤
-
先运行等待进程
打开一个命令行窗口,执行SemaphoreWait.exe
你会看到:[等待进程 PID=1234] 正在等待信号量...程序会卡住(因为信号量初始计数为 0),此时它处于阻塞状态。
-
再运行释放进程
打开另一个命令行窗口,执行SemaphoreSignal.exe
显示:[释放进程 PID=5678] 准备释放信号量,按回车键执行 V 操作...按回车键后,释放进程执行
ReleaseSemaphore,信号量计数从 0 变为 1。 -
观察等待进程
第一个窗口中的等待进程会立即从WaitForSingleObject返回,继续执行,打印:[等待进程] 获得信号量,进入临界区 [等待进程] 模拟工作 2 秒... [等待进程] 工作完成,释放信号量并退出。 -
再次测试
如果你想再次测试,可以再运行一次SemaphoreWait.exe,它会再次阻塞,然后运行SemaphoreSignal.exe释放即可。

浙公网安备 33010602011771号