• C++ 并发编程指南(8)线程间通信



    前言

    多线程编程中,线程间通信(Inter-Thread Communication,简称ITC)是不可或缺的一部分,它使得不同的线程能够交换信息、协作完成任务。C++作为一种功能强大的编程语言,提供了多种机制来实现线程间的通信。下面我们将详细讨论这些机制。

    一、线程间通信

    1、共享内存

    共享内存是最直接也是最常见的线程间通信方式。在C++中,全局变量、静态变量以及堆上分配的对象都可以被多个线程访问。这些变量在内存中的位置是固定的,因此多个线程可以通过指针或引用来访问和修改它们。

    然而,共享内存也带来了同步问题。如果没有适当的同步机制,多个线程可能会同时读写同一个变量,导致数据竞争和不一致的结果。为了解决这个问题,C++提供了多种同步原语,如互斥锁、条件变量、以及读写锁等。这些同步原语可以确保在某一时刻只有一个线程访问共享变量,从而避免数据竞争。例如,使用std::mutex可以保护对共享变量的访问:

    #include 
    #include 
    #include 
    
    std::mutex mtx; // 全局互斥锁
    int shared_data = 0; // 共享数据
    
    void increment() {
        for (int i = 0; i < 10000; ++i) {
            std::lock_guard<std::mutex> lock(mtx); // 使用锁保护区域
            ++shared_data;
        }
    }
    
    int main() {
        std::thread t1(increment);
        std::thread t2(increment);
        
        t1.join();
        t2.join();
        
        std::cout << "Shared data: " << shared_data << std::endl; // 输出应为20000
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2、消息队列和管道

    除了共享内存外,还可以使用消息队列和管道来实现线程间的通信。这些机制允许线程通过发送和接收消息来交换信息,而不是直接操作共享内存。虽然C++标准库没有直接提供消息队列和管道的实现,但开发者可以使用第三方库或自定义数据结构来实现这些功能。例如,可以使用标准库中的std::queue和条件变量来实现一个简单的消息队列:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    std::queue<int> messages; // 消息队列
    std::mutex mtx; // 互斥锁
    std::condition_variable cv; // 条件变量
    
    bool stop = false; // 停止标志
    
    void sender() {
        for (int i = 0; i < 10; ++i) {
            std::unique_lock<std::mutex> lock(mtx);
            messages.push(i); // 向队列中添加消息
            cv.notify_one(); // 通知等待的接收者
        }
        {
            std::lock_guard<std::mutex> lock(mtx);
            stop = true; // 设置停止标志
        }
        cv.notify_all(); // 通知所有等待的接收者
    }
    
    void receiver() {
        while (true) {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, []{ return !messages.empty() || stop; }); // 等待消息或停止信号
            if (stop && messages.empty()) {
                break; // 如果收到停止信号且队列为空,则退出循环
            }
            int msg = messages.front(); // 获取消息
            messages.pop(); // 从队列中移除消息
            lock.unlock(); // 解锁以允许其他线程访问队列
            std::cout << "Received: " << msg << std::endl; // 处理消息
        }
    }
    
    int main() {
        std::thread sender_thread(sender);
        std::thread receiver_thread(receiver);
        
        sender_thread.join();
        receiver_thread.join();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在这个例子中,我们使用了std::queue作为消息队列,std::mutex用于保护队列的访问,std::condition_variable用于在发送者和接收者之间同步。发送者线程向队列中添加消息并通知接收者,接收者线程等待消息的到来并处理它们。

    3、条件变量

    在C++中,条件变量是一种同步原语,用于实现线程之间的通信。它允许一个或多个线程等待某个条件成立,当条件成立时,通知等待的线程继续执行。下面是一个简单的示例,展示了如何使用条件变量实现线程通信:

    #include 
    #include 
    #include 
    #include 
    
    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;
    
    void print_id(int id) {
        std::unique_lock<std::mutex> lck(mtx);
        while (!ready) { // 如果条件不满足,则等待
            cv.wait(lck); // 释放锁并等待条件变量的通知
        }
        // 条件满足,继续执行
        std::cout << "thread " << id << '
    ';
    }
    
    void go() {
        std::unique_lock<std::mutex> lck(mtx);
        ready = true; // 设置条件为真
        cv.notify_all(); // 通知所有等待的线程
    }
    
    int main() {
        std::thread threads[10];
        for (int i = 0; i < 10; ++i) {
            threads[i] = std::thread(print_id, i);
        }
    
        std::cout << "10 threads ready to race...
    ";
        go(); // 开始比赛
    
        for (auto& th : threads) {
            th.join();
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    4、信号量

    在C++中,信号量是一种用于实现线程间通信的同步原语。它允许多个线程之间共享一个计数器,当计数器的值大于0时,线程可以继续执行;当计数器的值为0时,线程将被阻塞,直到其他线程释放资源。以下是使用信号量实现线程通信的基本步骤:

    • 包含头文件
    • 创建一个std::counting_semaphore对象,并指定初始计数器的值。
    • 在需要等待资源的线程中,调用acquire()函数来请求资源。如果计数器的值大于0,则线程将继续执行;否则,线程将被阻塞。
    • 在需要释放资源的线程中,调用release()函数来增加计数器的值。这将唤醒一个正在等待资源的线程。
    • 被唤醒的线程将继续执行,直到计数器的值再次变为0。

    下面是一个简单的示例,展示了如何使用信号量实现线程通信:

    #include 
    #include 
    #include 
    
    std::counting_semaphore<2> sem; // 创建一个信号量,初始计数器为2
    
    void print_id(int id) {
        sem.acquire(); // 请求资源
        // 获取到资源后,继续执行
        std::cout << "thread " << id << '
    ';
        sem.release(); // 释放资源
    }
    
    int main() {
        std::thread threads[4];
        for (int i = 0; i < 4; ++i) {
            threads[i] = std::thread(print_id, i);
        }
    
        for (auto& th : threads) {
            th.join();
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这个示例中,我们创建了一个信号量sem,并将其初始计数器设置为2。然后创建了4个线程,每个线程都会请求资源。由于信号量的计数器最大为2,因此只有两个线程可以同时获得资源并继续执行。其他线程将被阻塞,直到有线程释放资源。

    5、问题剖析

    5.1、线程同步与线程通信间的区别?

    线程通信和线程同步是多线程编程中两个密切相关的概念,它们之间有着密切的联系。以下是具体分析:

    • 线程通信:它指的是线程之间传递信息的过程。在多线程环境中,线程之间可能需要交换数据或者状态信息,以协调各自的行为。例如,一个线程可能需要通知另一个线程某个事件已经发生,或者某个资源已经准备好。线程通信可以通过多种机制实现,如管道、信号量、消息队列等。
    • 线程同步:它是指确保线程之间有序访问共享资源的一种机制。由于多个线程可能同时访问相同的资源,同步机制如互斥锁、条件变量等被用来防止数据竞争和确保数据的一致性。线程同步的目的是为了协调线程的执行顺序,避免出现不一致的状态。

    总的来说,线程通信关注的是线程间信息的传递,而线程同步关注的是线程间访问共享资源的秩序。虽然它们在概念上有所区别,但在实际应用中往往是相辅相成的。

  • 相关阅读:
    基于JAVA-小型酒店管理系统-计算机毕业设计源码+数据库+lw文档+系统+部署
    yolo 系列代码使用讲解(yolov5-yolov7)
    CSS利用定位+margin实现元素居中
    NetApp FAS2554故障灯常亮case处理过程分享
    three.js之模型
    虚拟机使用linux常用问题(虚拟机操作系统:ubuntu 22.04LTS)
    SpringBoot部署到外部Tomcat无法注册到Nacos服务端
    面了三十个人,说说我的真实感受
    vue项目打包后使用reverse-sourcemap反编译到源码(详解版)
    java ftputils 模拟测试方法 有效
  • 原文地址:https://blog.csdn.net/cloud323/article/details/136620168