原子操作在多线程开发中经常用到,比如在计数器,序列产生器等地方,这类情况下数据有并发的危险,但是用锁去保护又显得有些浪费,所以原子类型操作十分的方便。
原子操作虽然用起来简单,但是其背景远比我们想象的要复杂。其主要在于现代计算系统过于的复杂:多处理器、多核处理器、处理器又有核心独有以及核心共享的多级缓存,在这种情况下,一个核心修改了某个变量,其他核心什么时候可见是一个十分严肃的问题。
同时在极致追求性能的时代,处理器和编译器往往表现的很智能,进行极度的优化,比如什么乱序执行、指令重排等,虽然可以在当前上下文中做到很好的优化,但是放在多核环境下常常会引出新的问题来,这时候就必须提示编译器和处理器某种提示,告诉某些代码的执行顺序不能被优化。
所以这里说到的原子操作,基本都包含我们三个方面所关心的语义:操作本身是不可分割的(Atomicity),一个线程对某个数据的操作何时对另外一个线程可见(Visibility),执行的顺序是否可以被重排(Ordering)。
一、legacy GCC __sync
据说在C++11标准出来之前,大家都诟病C++标准没有一个明确的内存模型,随着多线程开发的普及这个问题显得越来越迫切。当然各个C++编译器实现者也是各自为政,GCC自然是实用主义当道,于是根据Intel的开发手册老早就搞出了一系列的__sync原子操作函数集合,这也是被广大程序员最为熟悉常用的操作了吧,罗列如下:
type __sync_fetch_and_OP (type *ptr, type value, ...)
type __sync_OP_and_fetch (type *ptr, type value, ...)
bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
__sync_synchronize (...)
type __sync_lock_test_and_set (type *ptr, type value, ...)
void __sync_lock_release (type *ptr, ...)
上面的OP操作包括add、sub、or、and、xor、nand这些常见的数学操作,而type表示的数据类型Intel官方允许的是int、long、long long的带符号和无符号类型,但是GCC扩展后允许任意1/2/4/8的标量类型;CAS的操作有两个版本分别返回bool表示是否成功,而另外一个在操作之前会先返回ptr地址处存储的值;__sync_synchronize直接插入一个full memory barrier,当然你也可能经常见到像asm volatile(“” ::: “memory”);这样的操作。前面的这些原子操作都是full barrier类型的,这意味着:任何内存操作的指令不允许跨越这些操作重新排序。__sync_lock_test_and_set用于将value的值写入ptr的位置,同时返回ptr之前存储的值,其内存模型是acquire barrier,意味着该操作之后的memory store指令不允许重排到该操作之前去,不过该操作之前的memory store可以排到该操作之后去,而__sync_lock_release则更像是对前面一个操作锁的释放,通常意味着将0写入ptr的位置,该操作是release barrier,意味着之前的memory store是全局可见的,所有的memory load也都完成了,但是接下来的内存读取可能会被排序到该操作之前执行。可以这里比较绕,翻译起来也比较的拗口,不过据我所见,这里很多是用在自旋锁类似的操作上,比如:
static volatile int _sync;
static void lock_sync() {
while(__sync_lock_test_and_set(&_sync, 1));
}
static void unlock_sync() {
__sync_lock_release(&_sync);
}
其实这里的1可以是任何non-zero的值,主要是用作bool的效果。
二、C++11 新标准中的内存模型
上面GCC那种full barrier的操作确实有效,但是就像当初系统内核从单核切换到多核用大颗粒锁一样的简单粗暴,先不说这种形势下编译器和处理器无法进行优化,光要变量使其对他处理器可见,就需要在处理间进行硬件级别的同步,显然是十分耗费资源的。在C++11新标准中规定的内存模型(memory model)颗粒要细化的多,如果熟悉这些内存模型,在保证业务正确的同时可以将对性能的影响减弱到最低。
原子变量的通用接口使用store()和load()方式进行存取,可以额外接受一个额外的memory order参数,而不传递的话默认是最强模式Sequentially Consistent。
根据执行线程之间对变量的同步需求强度,新标准下的内存模型可以分成如下几类:
2.1 Sequentially Consistent
该模型是最强的同步模式,参数表示为std::memory_order_seq_cst,同时也是默认的模型。
-Thread 1- -Thread 2-
y = 1 if (x.load() == 2)
x.store (2); assert (y == 1)
对于上面的例子,即使x和y是不相关的,通常情况下处理器或者编译器可能会对其访问进行重排,但是在seq_cst模式下,x.store(2)之前的所有memory accesses都会happens-before在这次store操作。
另外一个角度来说:对于seq_cst模式下的操作,所有memory accesses操作的重排不允许跨域这个操作,同时这个限制是双向的。
2.2 Acquire/Release
GCC的wiki可能讲的不太清楚,查看下面的典型Acquire/Release的使用例子:
std::atomic<int> a{0};
int b = 0;
-Thread 1-
b = 1;
a.store(1, memory_order_release);
-Thread 2-
while (a.load(memory_order_acquire) != 1) /*waiting*/;
std::cout << b << '\n';
毫无疑问,如果是seq_cst,那么上面的操作一定是成功的(打印变量b显示为1)。
a. memory_order_release保证在这个操作之前的memory accesses不会重排到这个操作之后去,但是这个操作之后的memory accesses可能会重排到这个操作之前去。通常这个主要是用于之前准备某些资源后,通过store+memory_order_release的方式”Release”给别的线程;
b. memory_order_acquire保证在这个操作之后的memory accesses不会重排到这个操作之前去,但是这个操作之前的memory accesses可能会重排到这个操作之后去。通常通过load+memory_order_acquire判断或者等待某个资源,一旦满足某个条件后就可以安全的“Acquire”消费这些资源了。
2.3 Consume
这是一个相比Acquire/Release更加宽松的内存模型,对非依赖的变量也去除了happens-before的限制,减少了所需同步的数据量,可以加快执行的速度。
-Thread 1-
n = 1
m = 1
p.store (&n, memory_order_release)
-Thread 2-
t = p.load (memory_order_acquire);
assert( *t == 1 && m == 1 );
-Thread 3-
t = p.load (memory_order_consume);
assert( *t == 1 && m == 1 );
线程2的assert会pass,而线程3的assert可能会fail,因为n出现在了store表达式中,算是一个依赖变量,会确保对该变量的memory access会happends-before在这个store之前,但是m没有依赖关系,所以不会同步该变量,对其值不作保证。
Comsume模式因为降低了需要在硬件之间同步的数量,所以理论上其执行的速度会比之上面的内存模型快一些,尤其在共享内存大规模数据量情况下,应该会有较明显的差异表现出来。
在这里,Acquire/Consume~Release这种线程间同步协作的机制就被完全暴露了,通常会形成Acquired/Consume来等待Release的某个状态更新。需要注意的是这样的通信需要两个线程间成对的使用才有意义,同时对于没有使用这个内存模型的第三方线程没有任何作用效果。
2.4 Relaxed
最宽松的模式,memory_order_relaxed没有happens-before的约束,编译器和处理器可以对memory access做任何的re-order,因此另外的线程不能对其做任何的假设,这种模式下能做的唯一保证,就是一旦线程读到了变量var的最新值,那么这个线程将再也见不到var修改之前的值了。
这种情况通常是在需要原子变量,但是不在线程间同步共享数据的时候会用,同时当relaxed存一个数据的时候,另外的线程将需要一个时间才能relaxed读到该值,在非缓存一致性的构架上需要刷新缓存。在开发的时候,如果你的上下文没有共享的变量需要在线程间同步,选用Relaxed就可以了。
2.5 小结
看到这里,你对Atomic原子操作,应当不仅仅停留在indivisable的层次了,因为所有的内存模型都能保证对变量的修改是原子的,C++11新标准的原子应该上升到了线程间数据同步和协作的问题了,跟前面的LockFree关系也比较密切。
手册上也这样告诫菜鸟程序员:除非你知道这是什么,需要减弱线程间原子上下文同步的耦合性增加执行效率,才考虑这里的内存模型来优化你的程序,否则还是老老实实的使用默认的memory_order_seq_cst,虽然速度可能会慢点,但是稳妥些,万一由于你不成熟的优化带来问题,是很难去调试的。
三、C++11 GCC __atomic
GCC实现了C++11之后,上面的__sync系列操作就变成了Legacy而不被推荐使用了,而基于C++11的新原子操作接口使用__atomic作为前缀。
对于普通的数学操作函数,其函数接口形式为:
type __atomic_OP_fetch (type *ptr, type val, int memorder);
type __atomic_fetch_OP (type *ptr, type val, int memorder);
除此之外,还根据新标准提供了一些新的接口:
type __atomic_load_n (type *ptr, int memorder);
void __atomic_store_n (type *ptr, type val, int memorder);
type __atomic_exchange_n (type *ptr, type val, int memorder);
bool __atomic_compare_exchange_n (type *ptr, type *expected, type desired, bool weak, int success_memorder, int failure_memorder);
bool __atomic_test_and_set (void *ptr, int memorder);
void __atomic_clear (bool *ptr, int memorder);
void __atomic_thread_fence (int memorder);
bool __atomic_always_lock_free (size_t size, void *ptr);
bool __atomic_is_lock_free (size_t size, void *ptr);
从函数名,看起来意思也很明了吧,上面的带_n的后缀版本如果去掉_n就是不用提供memorder的seq_cst版本。最后的两个函数,是判断系统上对于某个长度的对象是否会产生lock-free的原子操作,一般long long这种8个字节是没有问题的,对于支持128位整形的构架就可以达到16字节无锁结构了。
Boost.Asio这里就不在罗列了,不过其中有一些例子比较好,基于内存模型的Wait-free的ring buffer、producer-customer的例子,可以去看看。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/7NT1NXcUVWyFTcDVGUbTLA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。