专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

Rust学习笔记(六十一)实例项目多线程Web服务器

  在上一节中我们学习了构建一个简单的单线程Web服务器。它每次只能接收并处理一个请求,如果处理过程比较耗时,那么整个系统的吞吐率就很低。这一节我们学习用线程池来对其进行改进。
  在我们的想象中,改进后的main函数应该是这样的: fn main() {     let listener = TcpListener::bind("127.0.0.1:7878").unwrap();      let tp = ThreadPool::new(4);      for stream in listener.incoming() {         let stream = stream.unwrap();         tp.execute(|| handle_connection(stream));     } }
  应该有一个线程池ThreadPool,调用其关联函数new可以创建一个自定义线程数的线程池。ThreadPool上还应该有一个execute方法,它接收闭包并传递给线程池内的线程,由线程调用闭包。 线程池§
  下一步就是完成一个基本的线程池。那线程池的数据结构是什么样的呢?首先线程池里应该有一系列的线程(可以存在Vec中)。为了便于管理这些线程,每个线程还应该带有一些额外的信息,比如id。 线程池(主线程)如何管理这些线程呢?我们之前在四十九节学习过Channel进行线程间通信,那么这里就可以使用Channel对线程进行管理: 线程池持有sender 线程内轮询receiver,若收到任务就立即执行
  暂时先考虑到这里,下面先进行编码:
  首先将线程和线程上的额外信息抽象为一个名为Worker的结构体 struct Worker {     id: usize,     thread: Option>, }
  这里使用Option来存放创建线程返回的JoinHandle,以便于后面停止线程池时可以取出JoinHandle调用上面的join方法等待现有的任务结束后再停止。下面要为其实现一个new方法,以便于构造Worker。 前面说过线程内要轮询Channel的receiver来接收并执行任务,所以new需要接收一个receiver。这个receiver是什么类型的呢? 首先channel在mpsc模块下,之前学过,mpsc是多个生产者单个消费者的缩写。但是这里是多个线程来消费同一个sender发送的消息,会造成数据竞争。所以需要使用Mutex来对receiver进行加锁。但是多个线程都持有同一个receiver的话,又涉及到多线程的多重所有权。之前第五十节学过的多线程的多重所有权的知识,使用Arc可以解决。所以这里receiver的类型是Arc>>。 这里的消息应该分为两类: 线程需要执行的正常任务 线程池要停止时的终止消息
  可以使用枚举来定义两个Message的变体来实现。其中正常的任务应该是一个闭包,该闭包是传入thread::spawn的参数,通过查看thread::spawn函数的定义,发现该闭包的类型是FnOnce() -> T + Send + "static。那么就可以定义消息和消息中的任务了: type Job = Box;//定义类型别名省略代码 enum Message {     NewJob(Job),     Terminate, }
  然后定义Worker和它的new方法,Worker中的线程是这样的: 通过loop循环不断接收消息 判断消息类型,正常任务则取出消息中的任务直接执行 若为终止消息则终止循环,使该线程结束 struct Worker {     id: usize,     thread: Option>, }  impl Worker {     fn new(id: usize, receiver: Arc>>) -> Worker {         let thread = thread::spawn(move || loop {             let message = receiver.lock().unwrap().recv().unwrap();             match message {                 Message::NewJob(job) => {                      println!("Worker {} got a job; executing.", id);                     job();                 }                 Message::Terminate => {                     println!("Worker {} was told to terminate.", id);                     break;                 }             }         });          Worker {             id: id,             thread: Option::Some(thread),         }     } }
  此处的loop为何不能写成while let循环呢?下面是while let的代码: while let Ok(job) = receiver.lock().unwrap().recv() { 	println!("Worker {} got a job; executing.", id);  	job(); }
  因为锁在循环块外获取,而while 表达式中的值在整个块一直处于作用域中,job() 调用的过程中其仍然持有锁,这意味着其他 worker 因无法获取到锁而不能接收任务。而loop循环时,我们在循环块内获取锁,lock 方法返回的 MutexGuard 在 let job 语句结束之后立刻就被丢弃了。这确保了 recv 调用过程中持有锁,而在 job() 调用前锁就被释放了,这就允许并发处理多个请求了。
  Worker到此为止就实现完了,下面看ThreadPool的实现: pub struct ThreadPool {     workers: Vec,     sender: mpsc::Sender, }  impl ThreadPool { 	//接收线程数量并返回对应的线程池     pub fn new(size: usize) -> ThreadPool {         assert!(size > 0);  		//先创建一个size大小的Vector         let mut workers = Vec::with_capacity(size);  		//创建channel的sender和receiver         let (sender, receiver) = mpsc::channel();  		//创建带锁的receiver的原子引用         let receiver = Arc::new(Mutex::new(receiver));  		//创建相应数量的Worker,并把对应的id和receiver传入其中         for id in 0..size {             workers.push(Worker::new(id, Arc::clone(&receiver)));         } 		//返回线程池         ThreadPool {             workers: workers,             sender: sender,         }     }  	//有任务时直接把任务通过sender发送到对应的receiver     pub fn execute(&self, f: F)     where         F: FnOnce() + Send + "static,     {         self.sender.send(Message::NewJob(Box::new(f))).unwrap();     } }
  main.rs中的handle_connection函数与上一节中的一致。cargo run运行,然后同时发送多个请求(可用jmeter或其它工具),输出: Worker 0 got a job; executing. Worker 2 got a job; executing. Worker 1 got a job; executing. Worker 3 got a job; executing. Worker 2 got a job; executing. Worker 0 got a job; executing. Worker 1 got a job; executing. Worker 3 got a job; executing. Worker 2 got a job; executing. Worker 1 got a job; executing. Worker 3 got a job; executing. Worker 0 got a job; executing. Worker 2 got a job; executing. Worker 1 got a job; executing. Worker 0 got a job; executing. Worker 3 got a job; executing. Worker 2 got a job; executing. Worker 1 got a job; executing. Worker 3 got a job; executing. Worker 0 got a job; executing. Worker 2 got a job; executing.
  发现确实有4个线程在执行任务。 为线程池实现Drop trait§
  前面的main函数: fn main() {     let listener = TcpListener::bind("127.0.0.1:7878").unwrap();      let tp = ThreadPool::new(4);      for stream in listener.incoming() {         let stream = stream.unwrap();         tp.execute(|| handle_connection(stream));     } }
  如果main函数执行完毕,一些变量就走出作用域,其中包括我们的ThreadPool,所以我们需要为其实现Drop trait。走出作用域时,等待现有的任务执行完再清理。但是在此之前需要先向各线程发出终止消息,让其跳出死循环。因为如果不跳出死循环,线程池中的线程就会一直陷在死循环里,而主线程会一直等待其执行完。
  实现Drop trait: impl Drop for ThreadPool {     fn drop(&mut self) {         for _ in &mut self.workers {             self.sender.send(Message::Terminate).unwrap();         }          println!("Shutting down all workers.");          for worker in &mut self.workers {             println!("Shutting down worker {}", worker.id);             if let Some(thread) = worker.thread.take() {                 thread.join().unwrap();             }         }     } }
  为了更好的理解为什么需要两个分开的循环,想象一下只有两个 worker 的场景。如果在一个单独的循环中遍历每个 worker,在第一次迭代中向通道发出终止消息并对第一个 worker 线程调用 join。如果此时第一个 worker 正忙于处理请求,那么第二个 worker 会收到终止消息并停止。我们会一直等待第一个 worker 结束,不过它永远也不会结束因为第二个线程接收了终止消息。死锁!

亲民定位,功能多元,美看mcombM2Plus电动牙刷为了保持健康的牙齿,我们都需要养成按时刷牙的好习惯。随着生活品质的不断提升,很多小伙伴都会选择一款电动牙刷,以实现高效的清洁牙齿。我的选择就是这款美看M2Plus电动牙刷。它定位亲量化分析最近4个季度国内增长最快的32个公司,截止2021Q2通过最近4个季度的一些核心指标来筛选国内最具潜力的公司!连续4个季度净利润增速和每股基本收益增速均超50,说明公司在最近4个月都保持非常好的成长性,2020年ROIC大于10,说明智能扫地机器人怎么选?UONI由利A1Pro您的绝佳选择目前这个时代里貌似选择独居的人群越来越多,科技的不断进步为我们的生活带来了更好的体验。对于独居人士来说,一款智能扫地机器人就显得很有必要,让你在下班回家后可以面对干净的地板与房间。行业深度工业软件深度梳理国外及国内CAE公司梳理一CAE的本质是物理学数学计算机科学工程学CAE即计算机辅助设计(ComputerAidedEngineering),狭义上主要指用计算机对工程和产品进行性能与安全可靠性分析,对其公司深度奥普特机器视觉零部件国产龙头,核心软硬件全覆盖一机器视觉零部件国产龙头,过去三年净利润复合增速为481。以光源为起点,机器视觉核心软硬件全覆盖奥普特成立于2006年,是智能制造核心零部件供应商,主要从事机器视觉核心软硬件产品的行业深度2021年Q2高瓴资本美股持股公司深度梳理分析8月16日,SEC公布了高瓴旗美股2021年截止Q2的数据。今天一起来树理一下其截止2021年Q2的情况及和2021年Q1的变化情况。一2021年Q2持股情况高瓴资本截止2021年公司深度皓元生物特色CRO企业,深耕分子砌块和工具化合物一国内CRO行业进入黄金发展期,分子砌块渗入新药开发全流程1CRO行业为助力药企研发而生,行业发展劲头十足CRO行业萌芽于20世纪70年代,最初是作为大型药企溢出的生产力而生,随着行业深度新三板精选层66家企业汇总,有望首批登陆北交所一北交所上市标准今天证监会发布了北京证券交易所上市公司证券发行注册管理办法(试行)(征求意见稿),来一起看一下相应的上市要求征求意见稿明确,目前企业想要在北交所申请IPO,必须是新量化分析2019年以来成长性最高的公司梳理汇总42家公司分析梳理2019年以来净利润增速稳定在50以上的公司,并且最近四个季度均保持50净利润同比增速筛选条件1)连续2年净利润增速超50,取2019年,2020年数据2)连续四个季度净利润增量化分析2019年后国内上市公司最具潜力的的28家高成长性公司筛选标准,在2019年1月1日以后上市的公司(共930个公司),兼顾最近两个季度的高成长性和过去2年增长的稳健性来筛选公司筛选条件1)连续2年收入增速超15,取2019年,2020约会大作战坎坷之路四季换了四个动画公司,第四季终于稳了约会大作战作为一部横跨轻小说动画漫画游戏四个领域的作品,整体评价很不错。尤其是备受网友们关注的约会大作战动画,也要即将迎来了第四季。继约会大作战前三季,以及狂三外传之后即将到来的第
4位英雄脑内结构图!貂蝉想太多,鲁班最真实,火舞很无奈对于王者荣耀这款游戏的玩家来说,游戏中的英雄都是他们获取胜利的工具。不管天美上线多少新英雄,也不管游戏中有多少强势英雄,只要是玩家觉得哪个英雄好用那自然就会选择哪个英雄。而选择完英玩家充值到心悦3,女友三观崩塌,怒斥你去和游戏谈恋爱吧!玩家充值到心悦3,女友三观崩塌,怒斥你去和游戏谈恋爱吧在游戏圈里撒狗粮的情侣到处都是,尤其是王者荣耀里面,经常能够在等待开始的对阵界面那里面看到许多甜蜜秀恩爱的存在。而在近日,网友DNF86怀旧版强势来袭,瞬间满级轻松刷本赫顿玛尔是地下城与勇士原城镇地图的区域之一。包括赫顿玛尔市政街赫顿玛尔中央广场赫顿玛尔旧城区赫顿玛尔后街4个城镇区域,以及格兰之森格兰之森山麓月光酒馆诺斯玛尔4个地下城区域艾尔文防虎牙直播TV版虎牙直播是一款专为游戏爱好者打造的直播平台,平台中汇聚了各路游戏大咖,期间针对不同的游戏类别,还有专业性直播主播和你在线互动,游戏类型更是囊括了电脑单机,手机网游等多数热门游戏,其一手好牌打得稀烂,曾经让我们引以为傲的香锅为何变成这副模样?哈喽,大家好,我是栗原游戏解说,每天都会带来不同的游戏资讯和攻略!前言说起麻辣香锅(RNG前打野),相信大家都会想到他的盲僧和皇子,曾经以一己之力带领RNG拿下MSI冠军,在那个时最新消息手游股上涨,游戏版号或将在近期恢复审批说起童年回忆,我想哈利波特绝对能占一席位,毕竟谁没幻想过那么美妙的魔法世界呢。然而近期的一则消息可能会恶心到哈利波特迷11月15日,有网易手游哈利波特魔法觉醒玩家爆料称,哈利波特魔剧本杀开局!他离奇坠楼死亡,迷雾重重,你能找出真相吗?玩家001号,您好欢迎您进入剧本杀模拟内测在这次的故事中您将扮演一位检察法医回到三年前的案发现场与死者进行对话并参加检察公开听证会找出悬而未决的案件真相为保证游戏体验请打开BGM伴破晓杯DKG全胜战绩被破,弹幕出现不友好内容,赛后选手语出惊人游戏赛事全球化趋势越来越明显了,前有刚结束不久声势浩大的S11,现有小有名气且正比得如火如荼的破晓杯。破晓杯是拳头举办的第一个英雄联盟手游全球性的赛事,素来被人们认为是手游世界赛季吃鸡返场呼声最高的军需!冰雪女王返场后,终于轮到它们了务实不浮夸!我是你们的情报小能手,微笑十倍镜。和平精英每个赛季不仅有一周一套的新军需,而且还有老款军需的返场!从SS12开始这些活动就没有间断过,但是始终没有见到王者的归来!众所周永劫无间突破600万,国产买断制游戏还有下文?前途艰险,路在脚下600万这是国产买断制游戏的最新销量纪录,来自上线仅4个月的武侠大逃杀游戏永劫无间。顾名思义,买断制是指一次性付清游戏本体的价格,一般不在游戏中再度安排购买的游戏王者什么阵容优势最强,后期基本无敌的?很庆幸又来与大家分享游戏经验我是峡谷爱好者边路作曲家还是那句话,编辑不易,看完记得点个赞哟进入主题王者荣耀现一共有90几位英雄,每个段位都有自己的强势阵容,都是玩家喜欢选择的阵容,
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软件