C++中的管道和信号量详细教程及示例

在现代多进程、多线程编程中,管道信号量是两种常用的进程间通信(IPC)和同步机制。本文将详细介绍这两者的概念、工作原理,并通过 C++ 示例演示如何实现和使用它们。


一、管道(Pipe)

1.1 什么是管道?

管道 是一种进程间通信(IPC)机制,用于在两个进程之间传递数据。管道是 半双工 通信方式,意味着数据只能沿一个方向流动:一端写入,另一端读取。

管道使用两个文件描述符(fd):

  • 读端:用于从管道中读取数据。
  • 写端:用于向管道中写入数据。

1.2 管道的分类

  • 匿名管道:只能用于有亲缘关系的进程之间通信(例如父子进程)。通过 pipe() 函数创建。
  • 命名管道(FIFO):可以在任意两个无亲缘关系的进程之间通信。通过 mkfifo() 创建一个命名管道文件。

1.3 管道的工作原理

管道是操作系统提供的一个内存缓冲区,两个进程通过该缓冲区共享数据:

  • 写端:向管道中写入数据,当缓冲区满时,写操作会阻塞,直到有空位可写。
  • 读端:从管道中读取数据,当缓冲区为空时,读操作会阻塞,直到有新数据可读。

1.4 管道示例(父子进程通信)

下面的代码演示如何通过管道在父进程和子进程之间进行通信,父进程将消息写入管道,子进程从管道中读取消息。

#include 
#include  // for pipe, fork
#include   // for strlen
#include  // for wait

int main() {
    int pipefds[2];
    char buffer[30];

    // 创建管道
    if (pipe(pipefds) == -1) {
        std::cerr << "Pipe failed" << std::endl;
        return 1;
    }

    pid_t pid = fork();

    if (pid == -1) {
        std::cerr << "Fork failed" << std::endl;
        return 1;
    }

    if (pid == 0) {
        // 子进程:从管道读取数据
        close(pipefds[1]);  // 关闭写端
        read(pipefds[0], buffer, sizeof(buffer));  // 从管道读取数据
        std::cout << "Child process read: " << buffer << std::endl;
        close(pipefds[0]);  // 关闭读端
    } else {
        // 父进程:向管道写入数据
        close(pipefds[0]);  // 关闭读端
        const char* message = "Hello from parent";
        write(pipefds[1], message, strlen(message) + 1);  // 写入数据
        close(pipefds[1]);  // 关闭写端
        wait(NULL);  // 等待子进程结束
    }

    return 0;
}

1.5 代码解析

  • pipe(pipefds):创建匿名管道,pipefds[0] 是读端,pipefds[1] 是写端。
  • fork():创建子进程。父进程和子进程共享相同的管道文件描述符。
  • 父进程:关闭读端,通过写端将数据写入管道。
  • 子进程:关闭写端,从读端读取数据并输出。
  • wait(NULL):父进程等待子进程结束。

二、信号量(Semaphore)

2.1 什么是信号量?

信号量 是一种用于控制进程或线程访问共享资源的同步机制。信号量使用一个计数器来管理资源的可用数量,能够用于控制多个进程或线程对资源的访问。

  • 等待操作(P 操作,sem_wait():如果资源可用,获取资源并将计数器减 1;如果资源不可用,进程或线程会阻塞,等待资源变得可用。
  • 信号操作(V 操作,sem_post():释放资源,计数器加 1,并唤醒等待的进程或线程。

2.2 信号量的分类

  • 计数信号量:允许多个线程或进程同时访问一定数量的资源。
  • 二进制信号量:类似于互斥锁,最多只允许一个线程或进程访问资源。

2.3 信号量在多线程中的应用

信号量可以用于协调生产者和消费者模型,确保生产者在缓冲区满时等待,消费者在缓冲区空时等待。

2.4 信号量示例(生产者-消费者模型)

下面的代码使用信号量实现生产者-消费者模型。生产者将数据放入缓冲区,消费者从缓冲区中取出数据。信号量确保生产者和消费者正确地同步访问共享缓冲区。

#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 5  // 缓冲区大小
std::vector buffer;  // 缓冲区
sem_t sem_empty;  // 空槽信号量
sem_t sem_full;   // 满槽信号量
sem_t mutex;      // 互斥信号量,确保对缓冲区的互斥访问

void producer() {
    int item = 0;
    while (true) {
        item = rand() % 100;  // 生成数据

        sem_wait(&sem_empty);  // 等待空槽
        sem_wait(&mutex);      // 获取互斥锁

        // 将数据放入缓冲区
        buffer.push_back(item);
        std::cout << "Produced: " << item << std::endl;

        sem_post(&mutex);      // 释放互斥锁
        sem_post(&sem_full);   // 增加满槽计数

        std::this_thread::sleep_for(std::chrono::milliseconds(1000));  // 模拟生产时间
    }
}

void consumer() {
    int item;
    while (true) {
        sem_wait(&sem_full);  // 等待满槽
        sem_wait(&mutex);     // 获取互斥锁

        // 从缓冲区读取数据
        item = buffer.back();
        buffer.pop_back();
        std::cout << "Consumed: " << item << std::endl;

        sem_post(&mutex);     // 释放互斥锁
        sem_post(&sem_empty); // 增加空槽计数

        std::this_thread::sleep_for(std::chrono::milliseconds(1500));  // 模拟消费时间
    }
}

int main() {
    // 初始化信号量
    sem_init(&sem_empty, 0, BUFFER_SIZE);  // 表示缓冲区的空槽数
    sem_init(&sem_full, 0, 0);             // 表示缓冲区的满槽数
    sem_init(&mutex, 0, 1);                // 互斥信号量,确保对缓冲区的互斥访问

    // 创建生产者和消费者线程
    std::thread producer_thread(producer);
    std::thread consumer_thread(consumer);

    // 等待线程结束(实际不会结束)
    producer_thread.join();
    consumer_thread.join();

    // 销毁信号量
    sem_destroy(&sem_empty);
    sem_destroy(&sem_full);
    sem_destroy(&mutex);

    return 0;
}

2.5 代码解析

  • sem_init:初始化信号量。
    • sem_empty:表示缓冲区中的空槽位数,初始值为 BUFFER_SIZE
    • sem_full:表示缓冲区中的满槽位数,初始值为 0。
    • mutex:互斥信号量,初始值为 1,确保生产者和消费者互斥访问缓冲区。
  • 生产者线程:等待空槽,获取互斥锁,将数据写入缓冲区,然后释放互斥锁并增加满槽计数。
  • 消费者线程:等待满槽,获取互斥锁,从缓冲区读取数据,释放互斥锁并增加空槽计数。
  • sem_waitsem_post:确保信号量的同步,保证线程之间的正确协调。

你可能感兴趣的:(c++,c++全套攻略,c++多线程,c++,linux)