news 2026/8/29 4:31:21

【多线程】CSP模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【多线程】CSP模式

CSP(Communicating Sequential Processes)模型详解

Actor vs CSP 对比

Actor 模型: ┌─────────┐ ┌─────────┐ │ Actor A │ ──msg──►│ Actor B │ 每个 Actor 有自己的邮箱 │ [邮箱] │ │ [邮箱] │ 发送者不等待 └─────────┘ └─────────┘ CSP 模型: ┌─────────┐ ┌─────────┐ │ Process │◄───────►│ Process │ 进程通过 Channel 通信 │ A │ Channel │ B │ Channel 是独立的 └─────────┘ └─────────┘ │ │ └───────┬───────────┘ ▼ ┌─────────────┐ │ Channel │ Channel 是一等公民 │ [buffer] │ 可以有缓冲或无缓冲 └─────────────┘

CSP 核心概念

┌──────────────────────────────────────────────────────────────┐ │ CSP 模型 │ │ │ │ ┌─────────┐ ┌─────────────────┐ ┌─────────┐ │ │ │ Sender │───►│ Channel │───►│ Receiver│ │ │ │ │ │ ┌───┬───┬───┐ │ │ │ │ │ │ send() │ │ │ 1 │ 2 │ 3 │ │ │ recv() │ │ │ │ 可能阻塞 │ │ └───┴───┴───┘ │ │ 可能阻塞 │ │ │ └─────────┘ │ 有界缓冲区 │ └─────────┘ │ │ └─────────────────┘ │ │ │ │ 特点: │ │ 1. Channel 独立于进程存在 │ │ 2. 同一个 Channel 可被多个进程共享 │ │ 3. 发送/接收可以阻塞(同步点) │ └──────────────────────────────────────────────────────────────┘

完整代码实现

#include<condition_variable>#include<functional>#include<iostream>#include<mutex>#include<optional>#include<queue>#include<thread>#include<vector>// ============================================// Channel: The core of CSP model// A typed, thread-safe, bounded channel// Similar to Go's buffered channel// ============================================template<typenameT>classChannel{public:// ============================================// Constructor// capacity = 0 means unbuffered (synchronous)// capacity > 0 means buffered (async up to capacity)// ============================================explicitChannel(size_t capacity=0):capacity_(capacity),closed_(false){}// ============================================// Send a value into the channel// Blocks if buffer is full// Returns false if channel is closed// ============================================boolsend(T value){std::unique_lock<std::mutex>lock(mu_);// Wait until:// 1. There's room in buffer, OR// 2. Channel is closed// For unbuffered (capacity_=0): wait until someone is receivingcv_send_.wait(lock,[this](){returnclosed_||queue_.size()<capacity_||(capacity_==0&&receivers_waiting_>0);});// Can't send to closed channelif(closed_){returnfalse;}// Put value in queuequeue_.push(std::move(value));// Wake up one waiting receivercv_recv_.notify_one();// For unbuffered channel: wait until value is takenif(capacity_==0){cv_send_.wait(lock,[this](){returnqueue_.empty()||closed_;});}returntrue;}// ============================================// Receive a value from the channel// Blocks if buffer is empty// Returns std::nullopt if channel is closed and empty// ============================================std::optional<T>recv(){std::unique_lock<std::mutex>lock(mu_);// Increment waiting receivers (for unbuffered channel)++receivers_waiting_;cv_send_.notify_one();// Wake sender for unbuffered case// Wait until:// 1. There's data in buffer, OR// 2. Channel is closedcv_recv_.wait(lock,[this](){return!queue_.empty()||closed_;});--receivers_waiting_;// Channel closed and emptyif(queue_.empty()){returnstd::nullopt;}// Get value from queueT value=std::move(queue_.front());queue_.pop();// Wake up one waiting sendercv_send_.notify_one();returnvalue;}// ============================================// Close the channel// No more sends allowed, but can still receive remaining data// ============================================voidclose(){std::lock_guard<std::mutex>lock(mu_);closed_=true;cv_send_.notify_all();// Wake all senderscv_recv_.notify_all();// Wake all receivers}// ============================================// Check if channel is closed// ============================================boolis_closed()const{std::lock_guard<std::mutex>lock(mu_);returnclosed_;}private:mutablestd::mutex mu_;std::condition_variable cv_send_;// Senders wait on thisstd::condition_variable cv_recv_;// Receivers wait on thisstd::queue<T>queue_;// The buffersize_t capacity_;// Max buffer size (0 = unbuffered)boolclosed_;// Is channel closed?size_t receivers_waiting_=0;// Count of waiting receivers};// ============================================// Convenience operator for sending (like Go's ch <- value)// ============================================template<typenameT>Channel<T>&operator<<(Channel<T>&ch,T value){ch.send(std::move(value));returnch;}// ============================================// Convenience operator for receiving (like Go's value = <-ch)// ============================================template<typenameT>Channel<T>&operator>>(Channel<T>&ch,T&value){autoresult=ch.recv();if(result){value=std::move(*result);}returnch;}

Channel 类型对比

无缓冲 Channel (capacity = 0): ─────────────────────────────────────────────────────────────── 发送者 Channel 接收者 │ │ │ │── send(x) ──► │ │ │ [阻塞等待] │ │ │ │ ◄── recv() ────│ │ [数据直接传递] ───────┼──────────────────────────►│ │ [解除阻塞] │ │ │ │ │ → 同步通信,发送者必须等待接收者 有缓冲 Channel (capacity = 3): ─────────────────────────────────────────────────────────────── 发送者 Channel 接收者 │ ┌─────────┐ │ │── send(1) ──► │ [1] │ │ │── send(2) ──► │ [1][2] │ │ │── send(3) ──► │ [1][2][3]│ │ │── send(4) ──► │ [阻塞!] │ ◄── recv() ────────│ │ [等待空间] │ [2][3][4]│ 得到 1 │ │ [解除阻塞] └─────────┘ │ → 异步通信,缓冲满时才阻塞

使用示例

示例 1: 基本用法

intmain(){// Create a buffered channel with capacity 2Channel<int>ch(2);// Producer threadstd::threadproducer([&ch](){for(inti=1;i<=5;i++){std::cout<<"Sending: "<<i<<std::endl;ch.send(i);std::cout<<"Sent: "<<i<<std::endl;}ch.close();// No more data});// Consumer threadstd::threadconsumer([&ch](){while(true){autovalue=ch.recv();if(!value){std::cout<<"Channel closed"<<std::endl;break;}std::cout<<"Received: "<<*value<<std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));}});producer.join();consumer.join();return0;}

输出:

Sending: 1 Sent: 1 Sending: 2 Sent: 2 Sending: 3 ← 缓冲满,阻塞 Received: 1 Sent: 3 Sending: 4 Received: 2 Sent: 4 ... Channel closed

示例 2: 多生产者多消费者

intmain(){Channel<std::string>ch(5);// Multiple producersstd::vector<std::thread>producers;for(inti=0;i<3;i++){producers.emplace_back([&ch,i](){for(intj=0;j<3;j++){std::string msg="Producer"+std::to_string(i)+"-Msg"+std::to_string(j);ch.send(msg);}});}// Multiple consumersstd::vector<std::thread>consumers;for(inti=0;i<2;i++){consumers.emplace_back([&ch,i](){while(true){automsg=ch.recv();if(!msg)break;std::cout<<"Consumer"<<i<<" got: "<<*msg<<std::endl;}});}// Wait for producersfor(auto&t:producers)t.join();// Close channel after all producers donech.close();// Wait for consumersfor(auto&t:consumers)t.join();return0;}
多生产者多消费者模式: ┌────────────┐ │ Producer 0 │──┐ └────────────┘ │ │ ┌─────────────┐ ┌────────────┐ ┌────────────┐ ├────►│ Channel │────►│ Consumer 0 │ │ Producer 1 │──┤ │ [buffer] │ └────────────┘ └────────────┘ │ └─────────────┘ │ │ ┌────────────┐ ┌────────────┐ │ └───────────►│ Consumer 1 │ │ Producer 2 │──┘ └────────────┘ └────────────┘

示例 3: Pipeline 模式

// Pipeline: numbers → square → printintmain(){Channel<int>numbers(3);Channel<int>squares(3);// Stage 1: Generate numbersstd::threadgenerator([&numbers](){for(inti=1;i<=5;i++){numbers.send(i);}numbers.close();});// Stage 2: Square numbersstd::threadsquarer([&numbers,&squares](){while(auton=numbers.recv()){squares.send((*n)*(*n));}squares.close();});// Stage 3: Print resultsstd::threadprinter([&squares](){while(auton=squares.recv()){std::cout<<"Result: "<<*n<<std::endl;}});generator.join();squarer.join();printer.join();return0;}
Pipeline 模式: ┌───────────┐ numbers ┌───────────┐ squares ┌───────────┐ │ Generator │──────────────►│ Squarer │─────────────►│ Printer │ │ 1,2,3 │ Channel │ n → n² │ Channel │ 打印结果 │ └───────────┘ └───────────┘ └───────────┘ │ │ │ ▼ ▼ ▼ 1,2,3,4,5 1,4,9,16,25 Result: 1 Result: 4 Result: 9 ...

示例 4: Fan-out / Fan-in 模式

intmain(){Channel<int>jobs(10);Channel<int>results(10);// Fan-out: multiple workers process jobsstd::vector<std::thread>workers;for(intw=0;w<3;w++){workers.emplace_back([&jobs,&results,w](){while(autojob=jobs.recv()){// Simulate workstd::this_thread::sleep_for(std::chrono::milliseconds(50));intresult=(*job)*2;std::cout<<"Worker "<<w<<" processed "<<*job<<std::endl;results.send(result);}});}// Send jobsstd::threadsender([&jobs](){for(inti=1;i<=9;i++){jobs.send(i);}jobs.close();});// Fan-in: collect all resultsstd::threadcollector([&results](){intcount=0;while(autor=results.recv()){std::cout<<"Result: "<<*r<<std::endl;if(++count==9)break;// Know we have 9 jobs}});sender.join();for(auto&w:workers)w.join();results.close();collector.join();return0;}
Fan-out / Fan-in: ┌──────────┐ ┌───►│ Worker 0 │───┐ │ └──────────┘ │ ┌────────┐ jobs │ ┌──────────┐ │ results ┌───────────┐ │ Sender │──────────┼───►│ Worker 1 │───┼────────────►│ Collector │ └────────┘ Channel │ └──────────┘ │ Channel └───────────┘ │ ┌──────────┐ │ └───►│ Worker 2 │───┘ └──────────┘ 工作负载自动分配给空闲的 worker

Actor vs CSP 代码对比

// ==================== Actor 模型 ====================classCounter{intcount_=0;Actor actor_;public:voidincrement(){actor_.send([this](){++count_;});}voidget(std::function<void(int)>callback){actor_.send([this,callback](){callback(count_);});}};// 使用Counter counter;counter.increment();counter.increment();counter.get([](intv){std::cout<<v<<std::endl;});// ==================== CSP 模型 ====================voidcounter_process(Channel<std::string>&cmd,Channel<int>&result){intcount=0;while(autoc=cmd.recv()){if(*c=="inc"){++count;}elseif(*c=="get"){result.send(count);}}}// 使用Channel<std::string>cmd(1);Channel<int>result(1);std::threadt(counter_process,std::ref(cmd),std::ref(result));cmd.send("inc");cmd.send("inc");cmd.send("get");intvalue;result>>value;std::cout<<value<<std::endl;

总结

概念说明
Channel独立的通信管道,连接多个进程
有缓冲 Channel异步,缓冲满时阻塞发送者
无缓冲 Channel同步,发送者等待接收者
send()发送数据,可能阻塞
recv()接收数据,可能阻塞
close()关闭 Channel,不能再发送
Pipeline多个阶段串联处理
Fan-out/Fan-in多个 worker 并行处理
Actor vs CSPActorCSP
通信方式直接发给 Actor通过 Channel
邮箱归属属于 Actor独立存在
耦合度较高较低
典型语言Erlang, AkkaGo, Clojure
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 18:08:39

PyTorch安装教程GPU版:Ubuntu系统下的完整配置流程

PyTorch-CUDA-v2.8 镜像实战&#xff1a;Ubuntu 下的 GPU 加速深度学习环境搭建 在深度学习项目中&#xff0c;最让人头疼的往往不是模型设计&#xff0c;而是环境配置。你是否经历过这样的场景&#xff1a;代码写好了&#xff0c;却因为 torch.cuda.is_available() 返回 False…

作者头像 李华
网站建设 2026/8/24 6:28:14

SSH Reverse Tunnel反向隧道:暴露本地PyTorch服务

SSH Reverse Tunnel反向隧道&#xff1a;暴露本地PyTorch服务 在深度学习项目开发中&#xff0c;一个常见的困境是&#xff1a;你正在自己的工作站上调试一个基于 PyTorch 的模型服务&#xff0c;可能还用上了 Jupyter Notebook 做可视化实验分析。一切运行良好&#xff0c;但问…

作者头像 李华
网站建设 2026/8/24 6:27:58

C#之跨线程调用UI

C#之跨线程调用UI 正常多线程修改&#xff0c;报错private void button1_Click(object sender, EventArgs e){Thread thread new Thread(Test);thread.Start();}public void Test(){label1.Text "HelloWorld";}需要添加Invoke:同步更新UIprivate void button1_Clic…

作者头像 李华
网站建设 2026/8/26 18:42:09

别急着算距离——聊聊《最短单词距离 II》背后的工程思维

别急着算距离 ——聊聊《最短单词距离 II》背后的工程思维 作者:Echo_Wish 一、先说个扎心的现实: 这题考的不是算法,是“你会不会为未来买单” 第一次看到 Shortest Word Distance II,很多同学的反应是: “不就是算两个单词在数组里的最短距离吗?” 然后很自然地写出…

作者头像 李华
网站建设 2026/8/28 16:02:33

如何ping指定IP的端口号_ping 端口

如何 Ping 指定 IP 的端口号&#xff08;检测端口是否开放&#xff09; 普通的 ping 命令只能检测主机是否可达&#xff08;基于 ICMP 协议&#xff09;&#xff0c;无法检测指定端口&#xff08;如 80、443、3306 等&#xff09;。要“ping 一个端口”&#xff0c;实际上是检…

作者头像 李华
网站建设 2026/8/27 12:35:06

计算机视觉项目首选环境:PyTorch-CUDA-v2.8镜像实测推荐

PyTorch-CUDA-v2.8 镜像&#xff1a;计算机视觉项目的高效开发利器 在现代深度学习项目中&#xff0c;尤其是计算机视觉方向&#xff0c;一个稳定、开箱即用的开发环境往往决定了从原型验证到生产部署的速度。尽管 PyTorch 因其动态图设计和强大生态广受青睐&#xff0c;CUDA 提…

作者头像 李华