简介
本文介绍两种并发设计中常用的设计模式,包括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 主要功能就是通过状态机不断地切换状态监听想要处理的函数。