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;
}

运行演示步骤

  1. 先运行等待进程
    打开一个命令行窗口,执行 SemaphoreWait.exe
    你会看到:

     
    [等待进程 PID=1234] 正在等待信号量...

    程序会卡住(因为信号量初始计数为 0),此时它处于阻塞状态。

  2. 再运行释放进程
    打开另一个命令行窗口,执行 SemaphoreSignal.exe
    显示:

     
    [释放进程 PID=5678] 准备释放信号量,按回车键执行 V 操作...

    按回车键后,释放进程执行 ReleaseSemaphore,信号量计数从 0 变为 1。

  3. 观察等待进程
    第一个窗口中的等待进程会立即从 WaitForSingleObject 返回,继续执行,打印:

     
    [等待进程] 获得信号量,进入临界区
    [等待进程] 模拟工作 2 秒...
    [等待进程] 工作完成,释放信号量并退出。
  4. 再次测试
    如果你想再次测试,可以再运行一次 SemaphoreWait.exe,它会再次阻塞,然后运行 SemaphoreSignal.exe 释放即可。

 

posted @ 2026-06-09 23:20  [BORUTO]  阅读(10)  评论(0)    收藏  举报