在 Linux 下使用 C++ 进行信号量(semaphore)编程,通常涉及 POSIX 信号量(POSIX semaphores)。POSIX 信号量提供了一种用于进程间同步或线程间同步的机制。以下是如何在 C++ 中使用 POSIX 信号量的基本指南,包括创建、操作和销毁信号量。
POSIX 信号量的基本概念
POSIX 信号量分为两种类型:
- 命名信号量(Named Semaphores):通过名称进行标识,可以在不同进程之间共享。
- 未命名信号量(Unnamed Semaphores):通常用于线程间同步,存储在内存中。
本文将重点介绍命名信号量的使用方法。
使用步骤
- 包含必要的头文件
- 创建或打开信号量
- 初始化信号量
- 执行 P 操作(等待)和 V 操作(释放)
- 销毁信号量
示例代码
下面是一个使用命名 POSIX 信号量的示例程序,演示了如何在多个进程之间同步对共享资源的访问。
1. 创建一个共享内存段和信号量
首先,创建一个共享内存段,并在其中放置一个信号量。这里我们使用 shm_open 创建命名共享内存,并使用 sem_init 初始化信号量。
// semaphore_example.cpp
#include
#include
#include
#include
#include
#include
// 定义信号量和共享内存的名称
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 class="hljs-comment">// 关闭共享内存映射
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 class="hljs-built_in">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 class="hljs-built_in">munmap(shared_value, sizeof(int));
// 释放信号量
if (sem_post(sem) == -1) {
perror("sem_post");
}
close(shm_fd);
sem_close(sem);
// 等待子进程结束
wait(NULL);
}
return EXIT_SUCCESS;
}
2. 编译程序
使用 g++ 编译上述代码:
g++ -o semaphore_example semaphore_example.cpp
3. 运行程序
首先运行程序,它将创建共享内存和信号量:
./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 操作)释放信号量,允许其他进程访问共享资源。
详细说明
1. 创建或打开信号量
sem_t* sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);
SEM_NAME:信号量的名称,所有需要访问该信号量的进程必须使用相同的名称。O_CREAT:如果信号量不存在,则创建它。0644:权限模式,类似于文件权限。1:初始值,表示信号量的计数。
2. 初始化共享内存
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将共享内存映射到进程的地址空间。 - 初始化共享资源(例如,一个整数)。
3. 执行 P 操作和 V 操作
sem_wait(sem); // P 操作:等待信号量,计数减一
// 访问共享资源
sem_post(sem); // V 操作:释放信号量,计数加一
sem_wait:减少信号量的计数。如果计数为零,调用进程将被阻塞,直到有其他进程调用sem_post增加计数。sem_post:增加信号量的计数,并唤醒一个等待的进程(如果有)。
4. 销毁信号量
当不再需要信号量时,应该销毁它以释放资源:
sem_close(sem);
sem_unlink(SEM_NAME);
sem_close:关闭信号量的描述符。sem_unlink:删除命名信号量,只有当引用计数为零时才会真正删除。
注意事项
-
同步机制选择:
- 如果仅在单个进程的多个线程之间同步,可以考虑使用 C++11 提供的线程同步机制,如
std::mutex、std::condition_variable等,更加方便和安全。 - POSIX 信号量适用于跨进程的同步。
- 如果仅在单个进程的多个线程之间同步,可以考虑使用 C++11 提供的线程同步机制,如
-
错误处理:
- 始终检查系统调用的返回值,并进行适当的错误处理。
-
资源管理:
- 确保在不再需要时关闭信号量和共享内存,避免资源泄漏。
- 使用
sem_unlink删除命名信号量,防止僵尸信号量占用系统资源。
-
原子性操作:
- 信号量的 P 和 V 操作是原子的,确保了对共享资源的互斥访问。
使用 C++11 线程同步(可选)
如果你的应用仅涉及多线程同步,推荐使用 C++11 提供的同步机制,因为它们更易于使用且与 C++ 语言集成更好。例如:
#include
#include
#include
std::mutex mtx;
int shared_value = 0;
void increment() {
std::lock_guard lock(mtx) ;
++shared_value;
std::cout << "Incremented to " << shared class="hljs-function">void decrement() {
std::lock_guard lock(mtx) ;
--shared_value;
std::cout << "Decremented to " << shared class="hljs-function">int main() {
std::thread t1(increment);
std::thread t2(decrement);
t1.join();
t2.join();
return 0;
}
使用 std::mutex 和 std::lock_guard 可以简化同步操作,避免手动管理锁的获取和释放。
总结
POSIX 信号量是一种强大的进程间和线程间同步机制,适用于需要跨进程访问共享资源的场景。通过正确地创建、操作和销毁信号量,可以有效地控制对共享资源的访问,防止竞态条件和数据不一致的问题。然而,对于仅涉及多线程同步的应用,C++11 提供的同步机制更加简洁和高效,推荐优先使用。