C++并发编程-9. Actor和CSP设计模式

简介

本文介绍两种并发设计中常用的设计模式,包括Actor和CSP模式。传统的并发设计经常都是通过共享内存加锁保证逻辑安全,这种模式有几个缺点,包括1 频繁加锁影响性能,2 耦合度高。后来大家提出了Actor和CSP设计模式。

Actor设计模式

  • 简单点说,actor通过消息传递的方式与外界通信。消息传递是异步的。每个actor都有一个邮箱,该邮箱接收并缓存其他actor发过来的消息,actor一次只能同步处理一个消息,处理消息过程中,除了可以接收消息,不能做任何其他操作。
  • 每一个类独立在一个线程里称作Actor,Actor之间通过队列通信,比如Actor1 发消息给Actor2, Actor2 发消息给Actor1都是投递到对方的队列中。好像给对方发邮件,对方从邮箱中取出一样。如下图
  • Actor模型的另一个好处就是可以消除共享状态,因为它每次只能处理一条消息,所以actor内部可以安全的处理状态,而不用考虑锁机制。
  • 我们在网络编程中对于逻辑层的处理就采用了将要处理的逻辑消息封装成包投递给逻辑队列,逻辑类从队列中消费的思想,其实就是一种Actor设计模式。Erlang是天然支持Actor的语言。

CSP模式

  • CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,是一个很强大的并发数据模型,是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。相对于Actor模型,CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。go是天然支持csp模式的语言。

  • CSP和Actor类似,只不过CSP将消息投递给channel,至于谁从channel中取数据,发送的一方是不关注的。简单的说Actor在发送消息前是直到接收方是谁,而接受方收到消息后也知道发送方是谁,更像是邮件的通信模式。而csp是完全解耦合的。

无论Actor还是CSP,他们都有一个共同的特性"Do not communicate by sharing memory; instead, share memory by communicating"不要使用共享内存实现通信,而是通过通信实现共享内存

go风格的csp

C++风格的csp

  • C++是万能的,我们可以用C++实现一个类似于go的channel,采用csp模式解耦合,实现类似的生产者和消费者问题
cpp 复制代码
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class Channel {
private:
    std::queue<T> queue_;
    std::mutex mtx_;
    std::condition_variable cv_producer_;
    std::condition_variable cv_consumer_;
    size_t capacity_;
    bool closed_ = false;
public:
    Channel(size_t capacity = 0) : capacity_(capacity) {}
    
    bool send(T value) 
    {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_producer_.wait(lock, [this]() {
            // 对于无缓冲的channel,我们应该等待直到有消费者准备好
            return (capacity_ == 0 && queue_.empty()) || queue_.size() < capacity_ || closed_;
            });
        if (closed_) 
        {
            return false;
        }
        queue_.push(value);
        cv_consumer_.notify_one();
        return true;
    }
   
     bool receive(T& value) 
    {
        std::unique_lock<std::mutex> lock(mtx_);
        
        cv_consumer_.wait(lock, [this]() { return !queue_.empty() || closed_; });
        if (closed_ && queue_.empty())   // 为什么wait之后还需要再判断一下
        {
            return false;
        }
        value = queue_.front();
        queue_.pop();
        cv_producer_.notify_one();
        return true;
    }
   
    void close() 
    {
        std::unique_lock<std::mutex> lock(mtx_);
        closed_ = true;
        cv_producer_.notify_all();
        cv_consumer_.notify_all();
    }
};
// 示例使用
int main() {
    Channel<int> ch(10);  // 10缓冲的channel
    std::thread producer([&]() {
        for (int i = 0; i < 5; ++i) {
            ch.send(i);
            std::cout << "Sent: " << i << std::endl;
        }
        ch.close();
        });
    std::thread consumer([&]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 故意延迟消费者开始消费
        int val;
        while (ch.receive(val)) {
            std::cout << "Received: " << val << std::endl;
        }
        });
    producer.join();
    consumer.join();
    return 0;
}
  • 简单来说就是通过条件变量实现通信的阻塞和同步的。

利用csp思想实现取款逻辑

《C++并发编程实战》一书中提及了用csp思想实现atm机取款逻辑,我根据书中思想,整理了通信的示意图,书中部分代码存在问题,也一并修复了。

  • atm 主要功能就是通过状态机不断地切换状态监听想要处理的函数。

总结

相关推荐
tachibana211 分钟前
hot100 课程表(207)
java·数据结构·算法·leetcode
神仙别闹12 分钟前
编写基于C++ Huffman 算法的无损压缩程序和解压程序
java·c++·算法
炸膛坦客21 分钟前
单片机/C/C++八股:(二十三)#define 和 typedef 的区别
c语言·c++·单片机
IT乐手28 分钟前
Android 写本地日志工具类
android·java
阿维的博客日记43 分钟前
spring里面的@RequestParam什么意思
java·后端·spring
laowangpython44 分钟前
昆仑通态 Mcgs Pro软件安装步骤(附安装包)Mcgs Pro 3.3.6 下载安装教程(保姆级)
java·服务器·数据库·其他
旖-旎1 小时前
《LeetCode 646 最长数对链 || LeetCode 1143 最长公共子序列》
c++·算法·leetcode·动态规划
ttod_qzstudio1 小时前
【软考设计模式】命令模式:请求封装与调用接收解耦的精讲
设计模式·命令模式
罗超驿1 小时前
6.Java LinkedList深度解析:从链表原理到源码实践
java·开发语言·链表
2501_948106911 小时前
计算机毕业设计之jsp物业管理系统
java·大数据·开发语言·汽车·课程设计