深入理解和实现Windows进程间通信(信号量)

常见的进程间通信方法

常见的进程间通信方法有:

  1. 管道(Pipe)
  2. 消息队列
  3. 共享内存
  4. 信号量
  5. 套接字

下面,我们将详细介绍信号量的原理以及具体实现。

什么是信号量?

信号量(Semaphore)是一个非常重要的同步机制,用于控制多个线程或进程对共享资源的访问。其基本思想是使用一个整数变量来表示可用资源的数量,通过对这个整数的操作来控制资源的分配,从而实现对竞态条件的管理。信号量的操作是原子的,即在一个操作进行的过程中,不会被其他进程或线程中断。

信号量类型

信号量主要有两种类型:

  • 二进制信号量(或互斥锁):它的值只能是0或1,用来实现互斥,即在同一时刻只允许一个线程访问共享资源。
  • 计数信号量:可以取一个整数值,表示可用资源的数量。这允许多个线程同时访问同一资源,但总数不超过最大资源数。

主要操作

信号量有两个基本操作,通常被称为P(等待)操作和V(释放)操作。

  1. P操作(等待)
  • 当线程想要访问资源时,它会执行P操作。
  • 如果信号量的值大于0,表示有资源可用,信号量的值将减一,线程继续执行。
  • 如果信号量的值为0,线程将进入阻塞状态,等待信号量值大于0。
  1. V操作(释放)
  • 当线程释放资源时,执行V操作。
  • 信号量的值加一。
  • 如果有其他线程因信号量的值为0而阻塞,这些线程中的一个将被唤醒。

信号量在线程和进程中的区别

信号量这一概念可以应用在线程通信和进程通信上,基本的操作是一样的,都是PV操作,但在具体的实现和使用场景上还是有很大的区别的:

线程间的信号量

  • 存储位置:线程间使用的信号量通常存储在同一进程的内存空间,因为线程共享同一进程的地址空间。
  • 资源共享:线程信号量主要用于同一个应用程序内部的多个线程之间的同步,因为所有线程可以直接访问到相同的内存变量和数据结构。
  • 实现方式:在多线程环境中,可以使用较轻量级的同步机制,例如C++11提供的std::mutexstd::condition_variable等。

进程间的信号量

  • 存储位置:进程间信号量必须存储在所有相关进程都可以访问到的位置,通常是操作系统的内核空间。这样做是为了保证数据的一致性和安全性。
  • 资源共享:进程间信号量用于不同进程之间的同步,这些进程可能运行相同或不同的应用程序代码,它们彼此之间不共享内存空间。
  • 实现方式:需要操作系统提供的特定API支持,例如Windows的CreateSemaphoreReleaseSemaphore

接口介绍

创建信号量

CreateSemaphore

  • 功能:创建或打开一个命名或无名的信号量对象。
  • 声明
HANDLE CreateSemaphore( 
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, 
    LONG lInitialCount, 
    LONG lMaximumCount, 
    LPCWSTR lpName
)
  • 参数
    • lpSemaphoreAttributes:指向一个SECURITY_ATTRIBUTES结构,该结构决定返回的句柄是否可以被子进程继承。如果为NULL,则句柄不可继承。
    • lInitialCount:指定信号的初始计数值。
    • lMaximumCount:指定信号量的最大计数值。
    • lpName:信号量的名称,如果为空,则创建一个无名信号量。

等待信号量

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:需要关闭的信号量句柄对象。

实现

两个进程分别对一个存储在共享内存的整数进行累加,代码如下:

#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输出:
image.png
进程2输出:
image.png

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值