菜鸟笔记
提升您的技术认知

操作系统笔记

进程,线程,协程与并行,并发进程线程协程的区别死锁进程,线程,多线程i++的线程安全性同步和异步孤儿进程和僵尸进程/proc进程信息linux中的分段和分页互斥量 mutex线程进程间通信进程创建进程优先级进程的基础知识进程与线程的区别(面试题)线程的控制(创建,终止,等待,分离)可重入 VS 线程安全死锁的概念一级缓存和二级缓存的理解一句话解说内存屏障 Memory barrierbrk(), sbrk() 用法详解malloc/free函数的简单实现一文讲透 “进程、线程、协程”Linux进程状态线程池的陷阱linux内核学习之进程和线程进程与线程的区别和联系内存寻址linux IO子系统和文件系统读写流程Page cache和buffer cache的区别与联系漫谈linux文件IO多线程和多进程的区别内存泄漏字节、字、位、比特的概念和关系如何避免死锁ANSI是什么编码?CPU寻址范围(寻址空间)CPU 使用率低高负载的原因创建多少个线程合适操作系统下spinlock锁解析、模拟及损耗分析线程堆栈堆和栈的内存分配堆和栈的概念和区别堆和栈的区别,申请方式,程序的内存分配什么是 POD 数据类型Linux内存分配小结--malloc、brk、mmap系统调用与内存管理(sbrk、brk、mmap、munmap)进程描述和控制CPU执行程序的原理编译的基本概念Linux虚拟地址空间布局一个程序从源代码到可执行程序的过程程序的运行机制——CPU、内存、指令的那些事分页内存管理——虚拟地址到物理地址的转换深刻理解Linux进程间通信fork之后父子进程的内存关系fork之后,子进程继承了父进程哪些内容关于协程及其锁的一些认识对协程的一点理解std::thread join和detach区别CAS和ABA问题CAS算法锁和无锁无锁队列的实现Lock-Free 编程锁开销优化以及CAS进程、线程和协程之间的区别和联系多线程的同步与互斥(互斥锁、条件变量、读写锁、自旋锁、信号量)Linux 原来是这么管理内存的线程上下文切换怎么玩儿进程和线程通信原理CPU密集型 和 IO密集型cas原理以及Atomic原子类分析改变线程状态的八个方法六种进程间通信方式进程和线程的区别系统调用进程的概念linux共享内存进程间通讯方式进程的调度线程同步的方式mmap和shm 区别

进程间通讯方式

阅读 : 127

管道demo

进程间通讯方式

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cstdlib>

int main() {
    int pipefd[2];  // 用于存储管道的文件描述符
    pid_t pid;

    // 创建管道
    if (pipe(pipefd) == -1) {
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        exit(EXIT_FAILURE);
    }

    if (pid > 0) {  // 父进程
        // 关闭管道的写端
        close(pipefd[1]);

        // 从管道的读端读取数据
        char buffer[128];
        ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';  // 确保字符串以 null 结尾
            std::cout << "Parent received: " << buffer << std::endl;
        } else {
            std::cerr << "Failed to read from pipe" << std::endl;
        }

        // 关闭管道的读端
        close(pipefd[0]);
    } else {  // 子进程
        // 关闭管道的读端
        close(pipefd[0]);

        // 向管道的写端写入数据
        const char *msg = "Hello from child process!";
        ssize_t bytes_written = write(pipefd[1], msg, strlen(msg));
        if (bytes_written < 0) {
            std::cerr << "Failed to write to pipe" << std::endl;
        }

        // 关闭管道的写端
        close(pipefd[1]);
    }

    return 0;
}

命名管道demo

  1. 创建命名管道
    我们首先需要创建一个命名管道。这可以通过命令行工具 mkfifo 或在程序中使用 mkfifo() 系统调用来完成。

创建管道的命令行方式:

mkfifo /tmp/myfifo

创建管道的程序方式:

#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>

int main() {
    // 创建命名管道
    if (mkfifo("/tmp/myfifo", 0666) == -1) {
        perror("mkfifo");
        return -1;
    }
    std::cout << "Named pipe created at /tmp/myfifo" << std::endl;
    return 0;
}
  1. 写入者进程
    写入者进程向命名管道中写入数据。

writer.cpp:

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>

int main() {
    const char *fifo_path = "/tmp/myfifo";
    const char *message = "Hello from writer process!\n";

    // 打开管道文件(写模式)
    int fd = open(fifo_path, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    // 向管道写入数据
    if (write(fd, message, strlen(message)) == -1) {
        perror("write");
        close(fd);
        return -1;
    }

    std::cout << "Message sent: " << message << std::endl;

    // 关闭管道文件描述符
    close(fd);
    return 0;
}
  1. 读取者进程
    读取者进程从命名管道中读取数据。

reader.cpp

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>

int main() {
    const char *fifo_path = "/tmp/myfifo";
    char buffer[128];

    // 打开管道文件(读模式)
    int fd = open(fifo_path, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    // 从管道读取数据
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read == -1) {
        perror("read");
        close(fd);
        return -1;
    }

    buffer[bytes_read] = '\0';  // 确保字符串以 null 结尾
    std::cout << "Message received: " << buffer << std::endl;

    // 关闭管道文件描述符
    close(fd);
    return 0;
}

编译与运行
1. 创建管道:
如果没有提前创建管道,运行 writer 时会阻塞,直到管道被创建。
可以手动创建管道:bash复制

mkfifo /tmp/myfifo

或者运行创建管道的程序。

  1. 编译代码:
    将 writer.cpp 和 reader.cpp 分别保存为文件。
    使用以下命令编译:bash复制
g++ -o writer writer.cpp
g++ -o reader reader.cpp
  1. 运行程序:
    在一个终端运行读取者程序:
./reader
在另一个终端运行写入者程序:
./writer

输出示例

reader终端:Message received: Hello from writer process!
writer终端:Message sent: Hello from writer process!

注意事项

  1. 阻塞特性:
    • 默认情况下,读取者会阻塞,直到有数据可读。
    • 写入者会阻塞,直到有读取者打开管道。
    • 如果不希望阻塞,可以在 open() 时使用 O_NONBLOCK 标志。
  2. 管道文件路径:
    • 确保管道文件路径一致(这里是 /tmp/myfifo)。
    • 如果路径不存在,写入者会阻塞,直到管道被创建。
  3. 清理:
    • 使用完管道后,可以手动删除管道文件:bash复制
rm /tmp/myfifo
  1. 跨进程通信:
    • 命名管道允许不相关的进程之间通信,因此读取者和写入者可以是完全独立的程序。

通过这个示例,你可以看到命名管道如何实现进程间通信,非常适合需要跨进程传递数据的场景。

信号量

信号量(Semaphore)是一种用于进程间或线程间同步的机制,用于控制对共享资源的访问。它通过维护一个计数器来实现同步,当计数器大于零时,表示资源可用;当计数器为零时,表示资源已被占用。信号量通常用于解决互斥(Mutex)和同步(Sync)问题。

以下是信号量的基本操作:

  • P操作(Wait/Down/Decrease):将信号量的值减1。如果减1后信号量的值小于0,则调用进程或线程阻塞,等待信号量的值变为非负。

  • V操作(Signal/Up/Increase):将信号量的值加1。如果加1后信号量的值大于0,则唤醒一个等待该信号量的进程或线程。

  1. 信号量的分类
    信号量主要分为两种:
  • 二进制信号量(Binary Semaphore):值只能为0或1,用于互斥访问。
  • 计数信号量(Counting Semaphore):值可以是任意非负整数,用于控制多个资源的访问。
  1. 信号量的实现
    在C++中,信号量可以通过多种方式实现,包括系统V IPC信号量、POSIX信号量、Boost库以及C++20标准中的std::semaphore。

2.1 系统V信号量
系统V信号量是基于IPC机制的信号量实现,使用semget、semop等函数。

2.2 POSIX信号量
POSIX信号量是另一种实现方式,使用sem_open、sem_wait和sem_post等函数。

2.3 Boost信号量
Boost库提供了跨平台的信号量实现,使用boost::interprocess::named_semaphore。

2.4 C++20 std::semaphore
C++20标准引入了std::semaphore,使得信号量的使用更加简洁。

  1. 信号量的使用场景
    信号量主要用于以下场景:
  • 互斥(Mutex):确保多个线程或进程不会同时访问共享资源。
  • 同步(Sync):协调线程或进程的执行顺序,例如生产者-消费者问题。
  1. 注意事项

死锁:如果信号量的使用不当,可能会导致死锁。例如,多个线程或进程同时等待同一个信号量。
– 资源泄漏:在使用系统V或POSIX信号量时,需要确保信号量在程序结束时被正确删除,否则可能会导致资源泄漏。
– 性能:信号量的使用会引入上下文切换的开销,因此需要合理设计同步机制。

总结

信号量是一种强大的同步机制,适用于多种并发场景。根据具体需求,可以选择系统V信号量、POSIX信号量、Boost信号量或C++20标准中的std::semaphore。

消息队列demo

在C++中,消息队列是一种常见的进程间通信(IPC)机制,允许不同进程之间以异步方式交换消息。以下是关于C++消息队列的实现和使用方法的总结:

  1. 系统V消息队列
    系统V消息队列是一种传统的IPC机制,基于msgget、msgsnd和msgrcv等函数。

  2. POSIX消息队列
    POSIX消息队列提供了另一种实现方式,使用mq_open、mq_send和mq_receive等函数。

  3. Boost.Interprocess消息队列
    Boost库提供了跨平台的消息队列实现,基于共享内存。

  4. 自定义线程安全消息队列
    如果需要在多线程环境中使用消息队列,可以结合std::queue、std::mutex和std::condition_variable实现线程安全的消息队列。

总结

  • 系统V和POSIX消息队列适用于进程间通信,但需要处理IPC资源的创建和销毁。
  • Boost.Interprocess提供了跨平台的实现,基于共享内存。
  • 自定义线程安全消息队列适用于多线程环境。
  • 根据具体需求选择合适的消息队列实现方式。