在 Linux 下使用 C++ 进行信号量(semaphore)编程,通常涉及 POSIX 信号量(POSIX semaphores)。POSIX 信号量提供了一种用于进程间同步或线程间同步的机制。以下是如何在 C++ 中使用 POSIX 信号量的基本指南,包括创建、操作和销毁信号量。
POSIX 信号量分为两种类型:
本文将重点介绍命名信号量的使用方法。
下面是一个使用命名 POSIX 信号量的示例程序,演示了如何在多个进程之间同步对共享资源的访问。
首先,创建一个共享内存段,并在其中放置一个信号量。这里我们使用 shm_open
创建命名共享内存,并使用 sem_init
初始化信号量。
// semaphore_example.cpp
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#include <cstring>
// 定义信号量和共享内存的名称
const char* SEM_NAME = "/my_semaphore";
const char* SHM_NAME = "/my_shared_memory";
int main() {
// 创建或打开信号量
sem_t* sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);
if (sem == SEM_FAILED) {
perror("sem_open");
return EXIT_FAILURE;
}
// 创建或打开共享内存
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
if (shm_fd == -1) {
perror("shm_open");
sem_close(sem);
return EXIT_FAILURE;
}
// 设置共享内存大小
if (ftruncate(shm_fd, sizeof(int)) == -1) {
perror("ftruncate");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
}
// 映射共享内存
int* shared_value = static_cast<int*>(mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0));
if (shared_value == MAP_FAILED) {
perror("mmap");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
}
// 初始化共享资源
*shared_value = 0;
std::cout << "Shared resource initialized to " << *shared_value << std::endl;
// 关闭共享内存映射
munmap(shared_value, sizeof(int));
// 示例操作:多个进程对共享资源进行增减操作
pid_t pid = fork();
if (pid == -1) {
perror("fork");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
} else if (pid == 0) { // 子进程
// 等待信号量
if (sem_wait(sem) == -1) {
perror("sem_wait");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
}
// 访问共享资源
shared_value = static_cast<int*>(mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0));
if (shared_value == MAP_FAILED) {
perror("mmap");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
}
(*shared_value)++;
std::cout << "Child process incremented shared value to " << *shared_value << std::endl;
munmap(shared_value, sizeof(int));
// 释放信号量
if (sem_post(sem) == -1) {
perror("sem_post");
}
close(shm_fd);
sem_close(sem);
return EXIT_SUCCESS;
} else { // 父进程
// 等待信号量
if (sem_wait(sem) == -1) {
perror("sem_wait");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
}
// 访问共享资源
shared_value = static_cast<int*>(mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0));
if (shared_value == MAP_FAILED) {
perror("mmap");
close(shm_fd);
sem_close(sem);
return EXIT_FAILURE;
}
(*shared_value)--;
std::cout << "Parent process decremented shared value to " << *shared_value << std::endl;
munmap(shared_value, sizeof(int));
// 释放信号量
if (sem_post(sem) == -1) {
perror("sem_post");
}
close(shm_fd);
sem_close(sem);
// 等待子进程结束
wait(NULL);
}
return EXIT_SUCCESS;
}
使用 g++
编译上述代码:
g++ -o semaphore_example semaphore_example.cpp
首先运行程序,它将创建共享内存和信号量:
./semaphore_example
输出示例:
Shared resource initialized to 0
Parent process decremented shared value to -1
Child process incremented shared value to 0
在这个示例中:
sem_wait
(相当于 P 操作)等待信号量,确保互斥访问。sem_post
(相当于 V 操作)释放信号量,允许其他进程访问共享资源。sem_t* sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);
SEM_NAME
:信号量的名称,所有需要访问该信号量的进程必须使用相同的名称。O_CREAT
:如果信号量不存在,则创建它。0644
:权限模式,类似于文件权限。1
:初始值,表示信号量的计数。int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
ftruncate(shm_fd, sizeof(int));
int* shared_value = static_cast<int*>(mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0));
*shared_value = 0;
shm_open
创建或打开共享内存对象。ftruncate
设置共享内存的大小。mmap
将共享内存映射到进程的地址空间。sem_wait(sem); // P 操作:等待信号量,计数减一
// 访问共享资源
sem_post(sem); // V 操作:释放信号量,计数加一
sem_wait
:减少信号量的计数。如果计数为零,调用进程将被阻塞,直到有其他进程调用 sem_post
增加计数。sem_post
:增加信号量的计数,并唤醒一个等待的进程(如果有)。当不再需要信号量时,应该销毁它以释放资源:
sem_close(sem);
sem_unlink(SEM_NAME);
sem_close
:关闭信号量的描述符。sem_unlink
:删除命名信号量,只有当引用计数为零时才会真正删除。同步机制选择:
std::mutex
、std::condition_variable
等,更加方便和安全。错误处理:
资源管理:
sem_unlink
删除命名信号量,防止僵尸信号量占用系统资源。原子性操作:
如果你的应用仅涉及多线程同步,推荐使用 C++11 提供的同步机制,因为它们更易于使用且与 C++ 语言集成更好。例如:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_value = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++shared_value;
std::cout << "Incremented to " << shared_value << std::endl;
}
void decrement() {
std::lock_guard<std::mutex> lock(mtx);
--shared_value;
std::cout << "Decremented to " << shared_value << std::endl;
}
int main() {
std::thread t1(increment);
std::thread t2(decrement);
t1.join();
t2.join();
return 0;
}
使用 std::mutex
和 std::lock_guard
可以简化同步操作,避免手动管理锁的获取和释放。
POSIX 信号量是一种强大的进程间和线程间同步机制,适用于需要跨进程访问共享资源的场景。通过正确地创建、操作和销毁信号量,可以有效地控制对共享资源的访问,防止竞态条件和数据不一致的问题。然而,对于仅涉及多线程同步的应用,C++11 提供的同步机制更加简洁和高效,推荐优先使用。