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

Rust原子类型和内存排序

  简介
  原子类型在构建无锁数据结构,跨线程共享数据,线程间同步等多线程并发编程场景中起到至关重要的作用。本文将从 Rust提供的原子类型和原子类型的内存排序问题 两方面来介绍。 Rust原子类型
  Rust标准库提供的原子类型在std::sync::atomic模块下。Rust提供了AtomicBool, AtomicU8, AtomicU16, AtomicUsize等原子类型。下面我们以 AtomicUsize 为例介绍原子类型提供的原子操作。基本的load,store, swap原子操作就不过多介绍了。第一个要介绍的就是重要的compare-and-swap(CAS)原子操作,绝大部分无锁数据结构都或多或少依赖CAS原子操作。Rust提供的compare_and_swap接口如下: pub fn compare_and_swap(&self,     current: usize,     new: usize,     order: Ordering ) -> usize
  compare_and_swap接受一个期望的值和一个新的值,这里我们先忽略掉Ordering,后面会详细介绍,如果变量的值等于期望的值,就将变量值替换成新的值返回成功,否则不做任何修改并返回失败。compare_and_swap从语义上包含了读(load)语义和写(store)语义,先读出变量的值,和期望值比较,然后写入内存。原子操作保证了这三个步骤是原子的,在三个步骤之间不会插入其他指令从而导致变量值被修改。从1.50.0开始compare_and_swap被deprecated了,现在需要使用compare_exchange和compare_exchange_weak接口来实现CAS原子操作。 pub fn compare_exchange(     &self,     current: usize,     new: usize,     success: Ordering,     failure: Ordering ) -> Result  pub fn compare_exchange_weak(     &self,     current: usize,     new: usize,     success: Ordering,     failure: Ordering ) -> Result
  compare_exchange比compare_and_swap多了一个Ordering,两个Ordering分别作为CAS成功的Ordering和失败的Ordering,后面会有讲解,这里先跳过。从源代码可以看出compare_and_swap就是用compare_exchange实现的,只是compare_and_swap直接用成功情况下的Ordering生成在失败情况下的Ordering,compare_exchange则有更高的灵活性。 pub fn compare_and_swap(&self, current: $int_type, new: $int_type, order: Ordering) -> $int_type {     match self.compare_exchange(current,                                 new,                                 order,                                 strongest_failure_ordering(order)) {         Ok(x) => x,         Err(x) => x,     } }
  既然有了compare_exchange,那 compare_exchange_weak是做什么用的呢 ?从官方文档中可以看出两个API的唯一区别是compare_exchange_weak允许spuriously fail。那么什么是spuriously fail,在x86平台上CAS是一条指令完成的,这两个API在x86平台上效果没有区别,但是在arm平台上,CAS是由两条指令完成的LL/SC(Load-link/store-conditional),在arm平台下会发生spuriously fail,来自Wikipedia的解释
  Real implementations of LL/SC do not always succeed even if there are no concurrent updates to the memory location in question. Any exceptional events between the two operations, such as a context switch, another load-link, or even (on many platforms) another load or store operation, will cause the store-conditional to spuriously fail.
  简单的翻译就是,即使变量的值没有被更新LL/SC也不是100%成功,在LL/SC之间的异常事件如上下文切换,另外的LL,甚至load或者store都会导致spuriously fail。由于spuriously fail的存在,arm平台上compare_exchange是compare_exchange_weak加上一个loop实现的。通常我们在使用CAS的时候会把它放在一个loop中,反复重试直到成功,在这种情况下用compare_exchange_weak会获得一定的性能提升,如果用compare_exchange则会导致循环套循环。那我们该如何选择compare_change和compare_exchange_weak呢?如果你想在loop中使用CAS,绝大部分情况下使用compare_exchange_weak,除非你在每一次loop中做的事情很多,spuriously fail会导致很大的overhead即使它很少发生,这种情况下使用compare_exchange。再或者你使用loop就是为了避免spuriously fail,那直接使用compare_exchange就可以达到你的目的。
  接下来介绍另外一个重要的原子操作 fetch-and-add 。 pub fn fetch_add(&self, val: usize, order: Ordering) -> usize
  fetch_add也包含了读写两层语义,只是和CAS比起来它不关心变量当前的值,所以它一定成功。fetch_add一般用来做全局计数器。Rust提供了一系列的fetch_and_xxx操作,其中比较有趣的是fetch_update: pub fn fetch_update(     &self,     set_order: Ordering,     fetch_order: Ordering,     f: F ) -> Result where     F: FnMut(usize) -> Option,
  它会接受一个函数,并将函数应用到变量上,把生成的值写回变量中,因为CPU不支持类似的指令,所以其实fetch_update是使用CAS来实现原子性的。源代码如下,我们可以看这里使用的是compare_exchange_weak,因为它在一个loop中。 pub fn fetch_update(&self,                        set_order: Ordering,                        fetch_order: Ordering,                        mut f: F) -> Result<$int_type, $int_type> where F: FnMut($int_type) -> Option<$int_type> {     let mut prev = self.load(fetch_order);     while let Some(next) = f(prev) {         match self.compare_exchange_weak(prev, next, set_order, fetch_order) {             x @ Ok(_) => return x,             Err(next_prev) => prev = next_prev         }     }     Err(prev) }内存排序
  Rust提供了五种内存排序,由弱到强如下,并且内存排序被标记为#[non_exhaustive]表示未来可能会加入新的类型。 #[non_exhaustive] pub enum Ordering {     Relaxed,     Release,     Acquire,     AcqRel,     SeqCst, }
  Rust的内存排序和C++20保持一致。内存排序作用是通过限制编译器和CPU的reorder,来使得多个线程看到的内存顺序和我们程序所期望的一样,所以内存排序主要针对的是内存的读(load)写(store)操作。编译器和CPU会在编译时和运行时来reorder指令来达到提升性能的目的,从而导致程序中代码顺序会和真正执行的顺序可能会不一样,但是reorder的前提是不会影响程序的最终结果,也就是说编译器和CPU不会reorder相互有依赖的指令从而破坏程序原本的语义。比方说两条CPU指令,指令A读取一块内存,指令B写一块内存,如果CPU发现指令A要读取的内容在cache中没有命中需要去内存中读取,需要花额外的CPU cycle,如果指令B要操作的内存已经在cache中命中了,它可以选择先执后面的指令B。这时候内存排序的作用就体现出来了,内存排序告诉编译器和CPU哪些指令可以reorder哪些不可以。接下来分别介绍每一种内存排序的意义。 Relaxed: Relaxed Ordering不施加任何限制,CPU和编译器可以自由reorder,使用了Relaxed Ordering的原子操作只保证原子性。 // Global varible static x: AtomicU32 = AtomicU32::new(0); static y: AtomicU32 = AtomicU32::new(0); // Thread 1 let r1 = y.load(Ordering::Relaxed); x.store(r1, Ordering::Relaxed); // Thread 2 let r2 = x.load(Ordering::Relaxed); // A y.store(42, Ordering::Relaxed); // B
  这段程序是允许产生r1 == r2 == 42。按照正常的程序执行,这个结果看似不合理,但是因为使用了Relaxed Ordering,CPU和编译器可以自由reorder指令,指令B被reorder到指令A之前,那就会产生r1 == r2 == 42 Release: Release Ordering是针对写操作(store)的,一个使用了Release Ordering的写操作,任何读和写操作(不限于对当前原子变量)都不能被reorder到该写操作之后。并且所有当前线程中在该原子操作之前的所有写操作(不限于对当前原子变量)都对另一个对同一个原子变量使用Acquire Ordering读操作的线程可见。Release Ordering写和Acquire Ordering读要配对使用从而在两个或多个线程间建立一种同步关系。具体例子在介绍完Acquire之后一起给出。 Acquire: Acquire Ordering是针对读操作(load)的,一个使用了Acquire Ordering的读操作,任何读和写操作(不限于对当前原子变量)都不能被reorder到该读操作之前。并且当前线程可以看到另一个线程对同一个原子变量使用Release Ordering写操作之前的所有写操作(不限于对当前原子变量)。
  如果前面的例子中load使用Acquire Ordering,store使用Release Ordering,那么reorder就不会发生,r1 == r2 == 42的结果就不会产生。Acquire和Release动作特别像锁的加锁和释放锁的操作,因此Acquire Ordering和Release Ordering常被用在实现锁的场景。看下面的例子 // Global varible static DATA: AtomicU32 = AtomicU32::new(0); static FLAG: AtomicBool = AtomicBool::new(false); // Thread 1 DATA.store(10, Ordering::Relaxed); // A FLAG.store(true, Ordering::Release); // B // Thread 2 while !FLAG.load(Ordering::Acquire) {} // C assert!(DATA.load(Ordering::Relaxed) == 10); // D
  这段程序展示了两个线程之间同步的一种方式,在线程1中我们在共享内存中写入数据,然后把FLAG置成true,表明数据写入完成,在线程2中,我们用一个while循环等待FLAG被置成true,当FLAG被置成true之后,线程2一定会读到共享内存中的数据。线程1中的Release Ordering写和线程2中的Acquire Ordering读建立了顺序。当线程2跳出C行的循环表明它可以读到线程1在B行对FLAG的写入,按照Release-Acquire Ordering的保证,A行对DATA的写入不会被reorder到把FLAG置成true之后,并且对DATA的写入也会对线程2可见。假如这里没有使用Release-Acquire Ordering,那么线程对DATA的写入用可能会被reorder到写FLAG之后,那么线程2就会出现读到了FLAG但是读不到DATA的情况。 AcqRel: AcqRel Ordering主要用于read-modify-write类型的操作,比如compare_and_swap,表明了它同时具有Acquire和Release的语义,读的部分用Acquire Ordering,写的部分用Release Ordering。 SeqCst: SeqCst是Sequential Consistent的缩写,是一种最强的Ordering,在对读使用Acquire Ordering,对写使用Release Ordering,对read-modify-write使用AcqRel Ordering的基础上再保证所有线程看到所有使用了SeqCst Ordering的操作是同一个顺序,不论操作的是不是同一个变量。
  这里包含了两层意思,第一层意思 SeqCst禁止了所有的reorder ,针对内存读(load)写(store)的reorder一共有四种情况: loadload reorder:Arquire Ordering保证 loadstore reorder:Arquire Ordering和Release Ordering保证 storestore reorder:Release Ordering保证 storeload reorder:SeqCst Ordering保证
  看下面的例子 // Global varible static x: AtomicU32 = AtomicU32::new(0); static y: AtomicU32 = AtomicU32::new(0); // Thread 1 x.store(1, Ordering::SeqCst);  // A let r1 = y.load(Ordering::SeqCst); // B // Thread 2 y.store(1, Ordering::SeqCst); // C let r2 = x.load(Ordering::SeqCst);  // D
  这里如果不使用SeqCst Ordering就会出现r1 == r2 == 0的结果,原因是每一个线程中的load可以被reorder到store之前,即使我们分别对load和store使用Acquire Ordering和Release Ordering,因为它们都不保证storeload的reorder。
  SeqCst Ordering的第二层意思是 所有使用了SeqCst Ordering的操作在全局有一个顺序,并且所有的线程都看到同样的顺序 。比如说全局的顺序是A->B->C->D,那么r1 == 0 && r2 == 1,并且第三个线程如果看到了y == 1,那么它一定能看到x == 1,这就是SeqCst Ordering全局唯一顺序代表的意义。虽然使用SeqCst Ordering可以保证更严格的全局一致性,但是它也会带来性能损失,使用正确并且合适的内存排序才能获得最优的性能。
  最后解释一下compare_exchange两个Ordering的含义,CAS包含1.读变量,2.和期望值比较,3.写变量三个步骤,第一个Ordering表示CAS成功下即变量当前的值等于期望值时候,整个操作的Ordering,第二个Ordering表示如果当前比较失败了情况下,第一步读操作的Ordering。看一个用CAS实现自旋锁的例子 // Global lock static LOCK: AtomicBool = AtomicBool::new(false); // Thread 1 // Get lock while(LOCK.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err()) {} do_something(); // Unlock LOCK.store(false, Ordering::Release);  // Thread 2 // Get lock while(LOCK.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err()) {} do_something(); // Unlock LOCK.store(false, Ordering::Release);总结
  本文介绍了 Rust提供的原子类型,原子操作以及和原子操作配合使用的内存排序 。深入地理解内存排序才能写出正确并且性能最优的程序。内存排序是一个很深的话题,如有错误,欢迎指正,欢迎在评论区留言交流。

2019年浙江富婆征婚,结婚送宾利,网友却说不敢娶,如今怎样了?文异文录编辑异文录宾利姐冯萍自古道天下没有白吃的午餐。2019年,在浙江温州却发生了一件稀罕事,一位富婆,手里捧着多款豪车钥匙,一边炫耀一边高声道我话撂到这儿,谁要是和我结婚,我马15比11!中国女篮强势击败立陶宛,网友说这球可以,澎湃了内心真的很澎湃!看三人篮球不多,可是年少时打三人篮球却真不少,如此令人窒息的比赛节奏,却真不是我们在野球场可以感受到的。看比赛就能让人喘不过气来,更不用说场上比赛的姑娘们,不少时间梦华录周舍渣得那么明显,宋引章怎么就上套了?影视畅聊季前有周舍,后有沈如琢,梦华录中宋引章全然投入感情,奈何男方只把她当作可以炫耀的猎物。三姐妹中,赵盼儿头脑清醒,三娘大气沉稳,唯独一心恨嫁的宋引章让人叹息,让人着急。她怎么禁书载花船,因过分秽乱而被禁,流传最广的残本艳情小说载花船是清代的艳情小说,作者不提名。该书共计四卷十六回,现今已是残本,仅存三卷十二回。道光二十四年,同治七年,均被禁止流传。载花船简文介绍卷一明朝洪武年间,浙江处州有个国子监司业名国乒6人惨遭淘汰!连续3场输给韩国选手,日本世界冠军爆冷出局北京时间6月24日凌晨,乒乓球WTT斯洛文尼亚赛结束了首个正赛日的对决,目前国乒已有6位运动员在单打项目中惨遭淘汰,双打也有3对组合折戟沉沙,最近的几场外战出现了连输韩国选手3场,再婚孩子养不熟,丈夫前妻欲回归,后妈该如何逆袭?最近在播的情感短剧再婚,你们都看了吗?看过的人都反应真的有被气到,这个情感短剧里的后妈和孩子都太委屈,让人心疼。也不由感慨女性要承担的角色千千万,成为后妈之后更是格外的难!后妈对丈老人当街偷拍女生裙底,被抓后殴打巡防人员,态度嚣张拒不认错近日,在江苏无锡出现一件让人匪夷所思的事情。一位老人在大庭广众之下偷拍多张女性裙底的照片。被赶来的巡防人员当场看到当场抓住。然而这个老人拒不承认自己的错误,还倒打一耙说这位巡防人员丹东警方,上了热搜话说丹东今天卷入了争议的漩涡。这警方的一则通告和现场执法视频,上了热搜。作为一个长期独立观察这个世界的作者,我默默四下溜达了一圈,现场视频警方通告博主和普通人的意见都看了,现在和大缺钾比缺钙危险?3个信号证明你缺钾了,建议吃这几种食物来缓解如今,补钙的重要性从老到少,大多数人群都是知晓的。儿童长个子需要补钙老人预防骨质疏松也要补钙抽筋也需要适当地增加钙元素的摄入但是,殊不知身体缺钾带来的危害并不比缺钙少。五十多岁的王面积相当于10个颐和园!这里将成北京最大绿肺在北京城区东北方向20公里外,有一处仍在建设中的公园,地跨北京朝阳顺义昌平三区,建成后的面积相当于10个颐和园。从2020年9月1日温榆河公园朝阳示范区建成开园,及2021年9月2早上别再吃油条包子了,试试这6道营养早餐,金黄软糯,真美味一日之计在于晨,清晨是一天中最美好的时光,不仅要锻炼好,也要吃得好吃得全面。尤其是中老年人,身体随着年龄的增长,每天早餐可不能随便应付,既要吃得营养均衡,还要吃得清淡少油,不增加对
折叠屏市场持续火爆,苹果魅族有望加入战局,最快明年推出?相信关注数码圈的小伙伴都有注意到,近两年来折叠屏市场是愈发火爆,除了最早入局的三星之外,诸如华为小米OPPO等国产厂商,也都陆续入局,并推出了自家的折叠屏产品,备受广大消费者的关注2022近乎完美的4款手机,性能强悍配置拉满,关键价格良心2022近乎完美的4款手机,性能强悍配置拉满,关键价格良心。第1款realmeGTNeo32099起同样搭载天玑8100处理器,满血版LPDDR5运存UFS3。1闪存,性能输出更加个人养老金公募产品名录出炉作者丨姜诗蔷编辑丨李新江个人养老金公募产品名录正式出炉。11月11日,易方达广发嘉实华夏工银瑞信汇添富银华鹏华交银施罗德中欧等十多家基金公司旗下的多只养老目标基金发布了增设Y类基金油价调整消息今天11月12号,加油站调价后929598汽油售价最近连续三次油价调整最终都是以上涨结束,本次油价调整好像迎来了下跌的曙光,虽然现在油价预计调整幅度还是处于搁浅状态,而肉眼可见的原油价格上涨及时扑灭了油价预计调整幅度的下跌苗头,此01999元手机性价比排名OPPO两款手机上榜头条创作挑战赛打从国产手机起步开始,就离不开性价比这三个字,其实很好理解,大家在购买东西的时候,最关注的部分只能是价格,价格接受了,我们才会去看配置和设计,最后再给出评价这款手机是英超豪门悲喜夜!曼城爆冷翻车,热刺43逆转,切尔西3连败世界杯的脚步越来越近,但五大联赛还在激战当中。北京时间11月13日,英超第16轮全面开踢,曼城热刺利物浦切尔西和阿森纳5大豪门轮番登场。曼城12布伦特福德周中的联赛杯,瓜迪奥拉进行王治郅的坎坷情史23岁娶离异女总裁,42岁再婚90后北大女硕士文体育风云录编辑体育风云录篮球天才中国篮球高塔亚洲之王打住打住,我们说的这一位可不是小巨人姚明!这些标签更早出现,是在曾经的篮球传奇王治郅身上。作为男篮里第一个打进NBA的球员,王龚翔宇彻底休战,青春风暴再度刮起,球迷戏言江苏队心狠手辣龚翔宇再度休战,施海荣看起来已经放心了,小宇可以好好养一养,让伤病彻底地好起来。其实,把接应位置交给周页彤真没错。在亚洲杯面对越南的冲击时,主攻线哑火,正是她站出来扛起球队在去年的卢欧文在进攻端没有弱项他之前让我的工作轻松了很多直播吧11月13日讯今日快船不敌篮网,早些时候快船主帅泰伦卢接受媒体采访时谈到了旧将欧文。卢表示我可以告诉你,对我来说,欧文让我的工作轻松了很多,只要把篮球放在他的手中,他就能打出霍华德正式到达中国台湾!搂着4位空姐合影太开心,薪资令人羡慕前NBA巨星被球迷称为魔兽的霍华德于10日空降中国台湾省会台北机场,受到了来自现场数百名球迷的热烈欢迎。不仅如此,霍华德还受到了极高的礼遇,为了让其顺利通行,机场特意为他开启了绿色泰山再次领先,鹿死谁手难料中超联赛再次开打,第25轮中周6最早开球的泰山主场对河北队的比赛中,泰山队在上半场闷平之后,下半场的62分钟到65分钟分别由克雷桑陈蒲和吴兴涵连进3球,彻底扼杀了河北队爆冷平局的可