范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

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 结束,不过它永远也不会结束因为第二个线程接收了终止消息。死锁!

四大射手再降温一个,发育路该何去何从?目前,高分段有四名常驻射手,分别是马可波罗,公孙离,狄仁杰和李元芳。由于各方面机制还不错,前中期就有着不错的战斗力,不像其他站桩射手那样,需要憋三件套才能有所作为,所以,他们也被叫真当国服随便割?lolm退游潮不断,玩家毫不留恋lolm在国内上线已经有一段时间了,但是之前人们预料的火爆场面却并没有出现,反倒是出现了一波又一波的退游潮,并且玩家们离去的毫不拖泥带水,都是非常的绝决,归根结底都是因为游戏里各种新小本子发布两天,购买数量超过三十万最近因为新本子的发行,让刀友们都兴奋了起来,特别是因为小黑至宝和米拉吉身心这两个内容,值得氪一波,因为如果不氪的话那就得浑身长满肝才行,对于已经有了事业的刀友们来说显然是扛不住的,小虎说Cryin今年LPL前三,姿态Letme赞成,真有那么强吗?兄弟们应该已经知道了RNG下赛季的阵容都有谁了,不得不说小虎的职业精神确实值得尊重,上一年为了队伍直接转上,今年为了能让阿斌来RNG,小虎又放弃了刚刚打出成绩的上单位置选择回到了中深刻而不深沉,平淡而不平庸今天给大家介绍一下刘备这个战士英雄,虽然说是战士但是拉出来都是打野位置,因为这个英雄打边路的话没什么优势打野的话伤害可能还高一点,这个英雄价值13888金币,目前这个英雄有4款皮肤萌七现身CFS世界赛线下活动,萌七我也能跳到位要说全网直播平台哪里的舞蹈女主播最好看,可能大家第一时间都会想到的是斗鱼舞蹈区。要知道如今的社会,美女如云,大量热爱舞蹈的美女主播们涌入各大平台,她们或轻歌曼舞,或动感活力,尤其是一队三韩援!为了队内沟通顺畅,本土选手开设韩语课堂开始学习一队三韩援LPL战队有外援限制,而且这几年以来,韩援的数量变少了。本来各大战队喜欢引入韩援,两个名额都要用上。但现在很多战队已经成了全华班,没一个韩援。不止如此,我们LPL的本土选游戏引擎的竞争游戏行业的工具引擎需要不断的高研发投入,当前的工具引擎主要是Unity和UE的竞争。端游时代,UE的Unreal比Unity有游戏,但到了手游时代,Unity的易用性比Unreal狂暴传奇封号天赋怎么加复古合击寒刀封号天赋攻略狂暴传奇(同名蓝月合击复古合击寒刀怒火合击)三端互通手游,今天给大家介绍一下封号天赋的加点攻略,狂暴传奇礼包福利滴滴。天赋点分为防御和攻击两块,当然都需要点,不过狂暴传奇因为职业组爆冷!小超梦单吃369,莎莉男枪差点超神JDG不敌HYA主播队德玛西亚杯进入到了第二日的赛程,在当日的赛程中虎牙不仅仅继续进行了独播,还派出了HYA战队参赛!熟悉今年德杯赛制的小伙伴们应该知道,诸如HYA等主播队伍通过德杯预选赛的层层选拔之后无尽洪荒最新版无尽洪荒最新版是一款文字MUD的修仙手游,玩家的战斗之路就是修炼挂机刷图获取资源等,更快更容易的快一步成长,区别与传统的武侠类游戏,这款游戏既没有炫酷的打斗特效,也没有流畅的动作战
不知道怎么注册游戏交易平台我来教你!游戏平台首选什么?游戏现在已经成了大家最主流的消遣方式,手游,端游,网页游戏都慢慢地进入了大家的生活,游戏的分类也是五花八门,有动作游戏,冒险游戏,射击游戏,格斗游戏,角色扮演游戏,策略游戏,卡片游我的世界让MC变成RPG游戏,你也可以成为夷陵老祖我的世界中有非常多的模组作者创造出了非常多优秀的主题模组,拔刀剑就是其中一种。拔刀剑类的模组一出,就有不少作者玩家围绕着这一主题进行精细的加工,创造出与之相关的类型模组。今天就给大王者荣耀7英雄遭平衡调整,公孙离降温,其他英雄均是增强在12月14日,王者荣耀正式服又迎来了新一轮的版本更新,除了一些例行的更新内容外,有7名英雄在这次更新中遭到平衡调整,他们分别是孙膑钟馗公孙离杨玉环鲁班七号程咬金以及金蝉,其中公孙斗罗大陆手游开局选哪个武魂?蓝电霸王龙蓝银草和柔骨兔都行斗罗大陆开局选择蓝电霸王龙蓝银草和柔骨兔武魂,1。蓝电霸王龙为强攻系武魂,配合上血量较厚的近战武魂,战斗力会非常强悍2。蓝银草为回血类辅助武魂,且为远程角色,战斗中可以维持整支队伍魔兽赛季服一顿烧烤钱换祈福法杖值吗?某牧师玩家表示超值各位怀旧服的小伙伴你们好,我是细细说,很高兴又跟大家见面了。在魔兽世界60版本有一把牧师用的经典武器,名字叫祈福,同时它也可以被切换成暗牧玩法用的咒逐。之前怀旧服刚开放时,不少老牧英雄无敌3弩车全面解析各位小伙伴早上好,前面我们聊了一级和二级的宝物,小伙伴们有没有预期今天我们要聊三级宝物?不过令大家失望了,由于三级以上宝物非常强大,我就不再划分档次了,基本上都属于终极能用的。今天神仙打架!大作云集的2022年,期待值拉满!(第三期)2022年,游戏玩家狂欢之年(钱包掏空之年),咱们接着前几期的内容,讲讲2022年还有哪些大作发布。01神秘海域盗贼遗产合集据其他媒体报道,今年9月份的时候神秘海域盗贼遗产合集正式永劫无间12月14日更新了什么?12月14日更新内容分享永劫无间12月14日更新了什么?永劫无间服务器将于12月14日早7001100进行停服维护,这次更新内容十分丰富,今天为大家带来了永劫无间12月14日更新内容分享,一起来看看吧。永王者荣耀国服安琪拉最新出装铭文推荐Hello!大家好,我是柠檬,本期我们推荐上分的英雄是安琪拉。1技能和3技能的施法距离都非常长,也给了放风筝强杀敌人提供了保证。12技能消耗控制对手,面对残血敌人是可以在防御塔边缘阵型分享丨全球总冠军J。X。Tiger阵型呈上国人部落J。X。Tiger以全胜战绩成功加冕2021全球锦标赛总冠军总决赛三日比赛录播视频已经在部落冲突B站官方号上线前往B站搜索部落冲突ClashofClans回顾他们的夺冠时刻PGC复活赛17pero面临背水一战,谁能上演4am式剧本杀?随着2021PGC全球总决赛为期三周的预选赛结束,一共决出了12个参加总决赛的名额,还剩下的4个名额将会由本周四进行四场吃鸡赛制的复活赛决出。由于PCL赛区仅NH一支战队成功突围,