常见的进程间通信方法
常见的进程间通信方法有:
- 管道(Pipe)
- 消息队列
- 共享内存
- 信号量
- 套接字
下面,我们将详细介绍信号量的原理以及具体实现。
什么是信号量?
信号量(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
。
接口介绍
创建信号量
CreateSemaphore
- 功能:创建或打开一个命名或无名的信号量对象。
- 声明:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCWSTR lpName
)
- 参数:
- lpSemaphoreAttributes:指向一个
SECURITY_ATTRIBUTES
结构,该结构决定返回的句柄是否可以被子进程继承。如果为NULL,则句柄不可继承。 - lInitialCount:指定信号的初始计数值。
- lMaximumCount:指定信号量的最大计数值。
- lpName:信号量的名称,如果为空,则创建一个无名信号量。
- lpSemaphoreAttributes:指向一个
等待信号量
WaitForSingleObject
- 功能:等待信号量对象变为可用(即信号量的计数大于0),该函数就是信号量的P操作。
- 声明:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
)
- 参数:
- hHandle:信号量对象的句柄。
- dwMilliseconds:等待时间,可以是一个具体的毫秒数,或者使用特殊值
INFINITE
来表示无限等待。
释放信号量
ReleaseSemaphore
- 功能:释放信号量对象,增加信号量的计数,相当于信号量的V操作。
- 声明:
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
)
- 参数:
- hSemaphore:信号量对象的句柄。
- lReleaseCount:要增加的信号量计数。
- lpPreviousCount:可选参数,用于接收调用前的信号量计数。
关闭信号量句柄
CloseHandle
- 功能:关闭一个打开的对象句柄,例如信号量句柄。
- 声明:
BOOL CloseHandle( HANDLE hObject )
- 参数:
- hObject:需要关闭的信号量句柄对象。
- hObject:需要关闭的信号量句柄对象。
实现
两个进程分别对一个存储在共享内存的整数进行累加,代码如下:
#pragma once
// 进程1
#include <windows.h>
#include <iostream>
#include <ctime>
int semaphoreImpl() {
srand((unsigned)time(NULL));
// 创建或打开共享内存
HANDLE fileMapping = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
sizeof(int),
L"SharedMemoryForInteger");
if (fileMapping == NULL) {
std::cerr << "Could not create file mapping object: " << GetLastError() << std::endl;
return 1;
}
// 映射共享内存区域
int* sharedInt = (int*)MapViewOfFile(
fileMapping,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof(int));
if (sharedInt == NULL) {
std::cerr << "Could not map view of file: " << GetLastError() << std::endl;
CloseHandle(fileMapping);
return 1;
}
// 初始化共享整数
*sharedInt = 0;
// 创建或打开信号量
HANDLE semaphore = CreateSemaphore(
NULL,
1,
1,
L"MySemaphore");
if (semaphore == NULL) {
std::cerr << "CreateSemaphore error: " << GetLastError() << std::endl;
UnmapViewOfFile(sharedInt);
CloseHandle(fileMapping);
return 1;
}
while (*sharedInt < 10) {
WaitForSingleObject(semaphore, INFINITE); // 等待信号量, 即P操作
std::cout << "Incrementor1: Current value = " << *sharedInt << ". Incrementing..." << std::endl;
(*sharedInt)++;
Sleep(rand() % 1000 + 500); // 随机延时500到1500毫秒
ReleaseSemaphore(semaphore, 1, NULL); // 释放信号量,即V操作
}
// 清理
UnmapViewOfFile(sharedInt);
CloseHandle(fileMapping);
CloseHandle(semaphore);
return 0;
}
进程2的代码和上面代码几乎一致,除了下面这行代码:
std::cout << "Incrementor2: Current value = " << *sharedInt << ". Incrementing..." << std::endl;
结果
进程1输出:
进程2输出: