学懂C++(三十四):深入详解 C++ 高级多线程编程技术中的并发设计模式

引言

在现代软件开发中,多线程编程已成为提升性能和响应能力的重要手段。设计模式为解决并发问题提供了有效的解决方案。本文将探讨常见的并发设计模式,包括生产者-消费者模式、读者-写者模式、单例模式、帧-工作者模式以及Future-Task模式,并为每个示例提供详细的注释和解析,以帮助读者更好地理解其实现原理。

1. 生产者-消费者模式

1.1 概念

生产者-消费者模式涉及两个角色:生产者和消费者。生产者负责生成数据并将其放入缓冲区,而消费者则从缓冲区中取出数据进行处理。此模式的关键在于协调生产者和消费者之间的工作,以避免竞态条件和资源浪费。

1.2 示例及解析

以下是一个简单的生产者-消费者模式的实现:

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>

// 定义一个线程安全的缓冲区
std::queue<int> buffer; // 用于存放生产的数据
const unsigned int maxBufferSize = 5; // 定义缓冲区的最大大小
std::mutex mtx; // 用于保护缓冲区的互斥锁
std::condition_variable cv; // 用于条件变量通知

// 生产者线程函数
void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产过程
        std::unique_lock<std::mutex> lock(mtx); // 锁定缓冲区

        // 等待缓冲区有空间
        cv.wait(lock, [] { return buffer.size() < maxBufferSize; });
        buffer.push(i); // 生产数据
        std::cout << "Producer " << id << " produced: " << i << std::endl;
        cv.notify_all(); // 通知消费者有新数据可用
    }
}

// 消费者线程函数
void consumer(int id) {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费过程
        std::unique_lock<std::mutex> lock(mtx); // 锁定缓冲区

        // 等待缓冲区有数据
        cv.wait(lock, [] { return !buffer.empty(); });
        int value = buffer.front(); // 取出数据
        buffer.pop(); // 移除数据
        std::cout << "Consumer " << id << " consumed: " << value << std::endl;
        cv.notify_all(); // 通知生产者有空间可以生产
    }
}

int main() {
    std::thread p1(producer, 1); // 创建生产者线程
    std::thread c1(consumer, 1); // 创建消费者线程
    
    p1.join(); // 等待生产者线程结束
    c1.join(); // 等待消费者线程结束

    return 0;
}

1.3 运行结果分析

在这个示例中,生产者和消费者线程通过互斥锁和条件变量进行同步,确保了对共享缓冲区的安全访问。生产者在缓冲区满时等待,而消费者在缓冲区空时等待。

2. 读者-写者模式

2.1 概念

读者-写者模式允许多个读者并行访问资源,但在写者访问资源时,所有读者和其他写者必须被阻塞。这种模式适用于读操作远远多于写操作的场景。

2.2 示例及解析

以下是一个简单的读者-写者模式的实现:

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <chrono>

std::shared_mutex rwMutex; // 读写锁
int sharedData = 0; // 共享数据

// 读者线程函数
void reader(int id) {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟读操作
        rwMutex.lock_shared(); // 获取共享锁
        std::cout << "Reader " << id << " read: " << sharedData << std::endl; // 输出共享数据
        rwMutex.unlock_shared(); // 释放共享锁
    }
}

// 写者线程函数
void writer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 模拟写操作
        rwMutex.lock(); // 获取独占锁
        sharedData++; // 修改共享数据
        std::cout << "Writer " << id << " wrote: " << sharedData << std::endl; // 输出更新后的数据
        rwMutex.unlock(); // 释放独占锁
    }
}

int main() {
    std::thread r1(reader, 1); // 创建读者线程
    std::thread r2(reader, 2); // 创建另一个读者线程
    std::thread w1(writer, 1); // 创建写者线程
    
    r1.join(); // 等待读者线程结束
    r2.join(); // 等待读者线程结束
    w1.join(); // 等待写者线程结束

    return 0;
}

2.3 运行结果分析

在这个示例中,多个读者可以同时读取数据,而在写者写入数据时,读者会被阻塞,提高了性能。

3. 单例模式

3.1 概念

单例模式确保一个类仅有一个实例,并提供全局访问点。在线程安全的上下文中,特别需要确保在多线程环境下创建实例的安全性。

3.2 示例及解析

以下是一个线程安全的单例模式实现示例:

cpp 复制代码
#include <iostream>
#include <mutex>

// 单例类定义
class Singleton {
public:
    // 获取单例实例
    static Singleton& getInstance() {
        std::call_once(initFlag, []() { // 确保单例只被初始化一次
            instance.reset(new Singleton());
        });
        return *instance; // 返回单例实例
    }

    void displayMessage() {
        std::cout << "Singleton Instance Address: " << this << std::endl; // 输出实例地址
    }

private:
    Singleton() = default; // 私有构造函数
    static std::unique_ptr<Singleton> instance; // 存储单例实例的智能指针
    static std::once_flag initFlag; // 一次性标志
};

// 静态成员初始化
std::unique_ptr<Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;

int main() {
    std::thread t1([]() { Singleton::getInstance().displayMessage(); });
    std::thread t2([]() { Singleton::getInstance().displayMessage(); });

    t1.join(); // 等待线程t1结束
    t2.join(); // 等待线程t2结束

    return 0;
}

3.3 运行结果分析

运行结果显示无论创建多少线程,单例实例只会创建一个,保证了线程安全。

4. 帧-工作者模式

4.1 概念

帧-工作者模式是一种常见的并发设计模式,适用于需要将大量任务分配给多个工作线程处理的场景。通过将任务分成较小的块,将这些块分配给多个工作线程,可以实现高效的并发处理。

4.2 示例及解析

以下是帧-工作者模式的简单实现:

cpp 复制代码
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>

std::mutex mtx; // 用于保护输出的互斥锁

// 工作线程函数
void worker(int id, int start, int end) {
    for (int i = start; i < end; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 模拟工作
        std::lock_guard<std::mutex> lock(mtx); // 锁定互斥锁
        std::cout << "Worker " << id << " processing: " << i << std::endl; // 输出处理信息
    }
}

int main() {
    const int totalWork = 20; // 总工作量
    const int numWorkers = 4; // 工作线程数量
    std::vector<std::thread> workers; // 存储线程的向量

    int workPerThread = totalWork / numWorkers; // 每个线程处理的工作量
    for (int i = 0; i < numWorkers; ++i) {
        // 创建工作线程,分配不同的任务块
        workers.emplace_back(worker, i + 1, i * workPerThread, (i + 1) * workPerThread);
    }

    for (auto& worker : workers) {
        worker.join(); // 等待所有工作线程结束
    }

    return 0;
}

4.3 运行结果分析

在该示例中,多个工作线程可以并行处理不同的任务块,提升了效率。

5. Future-Task 模式

5.1 概念

Future-Task模式是一种用于异步编程的设计模式,允许任务在后台异步执行,并在需要时获取结果。这种模式通常与线程池结合使用,以提高资源利用率。

5.2 示例及解析

以下是 Future-Task 模式的实现示例:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

// 异步任务
int asyncTask(int id) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100 * id)); // 模拟耗时任务
    return id * 2; // 返回计算结果
}

int main() {
    // 使用 std::async 启动异步任务
    std::future<int> fut1 = std::async(std::launch::async, asyncTask, 1);
    std::future<int> fut2 = std::async(std::launch::async, asyncTask, 2);

    // 获取结果并输出
    std::cout << "Result from task 1: " << fut1.get() << std::endl; // 阻塞等待 task 1 完成
    std::cout << "Result from task 2: " << fut2.get() << std::endl; // 阻塞等待 task 2 完成

    return 0;
}

5.3 运行结果分析

在这个示例中,两个任务在后台异步执行,主线程可以在等待结果的同时继续处理其他逻辑。

6. 核心点总结与技术精髓

6.1 设计模式的核心点

  • 生产者-消费者模式:利用互斥锁和条件变量保证缓冲区的安全访问,避免竞争条件。
  • 读者-写者模式:允许多个读者并发访问,确保写者访问时的独占性,适用读多写少的场景。
  • 单例模式:确保全局只有一个实例,并提供线程安全的访问方式,适合需要共享资源的情况。
  • 帧-工作者模式:将工作分配给多个线程,提升处理效率,适合大规模并行处理。
  • Future-Task模式:支持异步执行和结果获取,通过将任务放入后台执行来优化资源利用率。

7. 结语

掌握并发设计模式是实现高效、多线程程序的关键。通过合理的设计和实现,我们能够有效地解决并发问题,提高程序的性能和可靠性。希望本文能够为您在 C++ 的多线程编程中提供有价值的参考和指导。

上一篇:学懂C++(三十三):深入详解 C++ 高级多线程编程技术中的并发数据结构

下一篇:学懂C++(三十五):深入详解C++ 多线程编程性能优化

相关推荐
羊小猪~~1 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
wrx繁星点点1 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
脉牛杂德2 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz2 小时前
STL--哈希
c++·算法·哈希算法
CSUC2 小时前
【C++】父类参数有默认值时子类构造函数列表中可以省略该参数
c++
Vanranrr2 小时前
C++ QT
java·c++·qt
鸿儒5172 小时前
C++ lambda 匿名函数
开发语言·c++
van叶~3 小时前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
金池尽干3 小时前
设计模式之——观察者模式
观察者模式·设计模式
knighthood20013 小时前
解决:ros进行gazebo仿真,rviz没有显示传感器数据
c++·ubuntu·ros