热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

std::atomic的锁在哪里?

如何解决《std::atomic的锁在哪里?》经验,为你挑选了3个好方法。

如果数据结构中包含多个元素,则它的原子版本不能(始终)无锁.我被告知这对于较大的类型是正确的,因为CPU不能在不使用某种锁的情况下以原子方式更改数据.

例如:

#include 
#include 

struct foo {
    double a;
    double b;
};

std::atomic var;

int main()
{
    std::cout <

输出(Linux/gcc)是:

0
16
16

由于原子和foo大小相同,我不认为锁存储在原子中.

我的问题是:
如果一个原子变量使用一个锁,它存储在哪里,这对该变量的多个实例意味着什么?



1> Peter Cordes..:

通常的实现是使用原子对象的地址作为键,互斥体的哈希表(或者甚至只是简单的自旋锁,没有回退到OS辅助的睡眠/唤醒).哈希函数可能就像使用地址的低位作为2次幂大小的数组的索引一样简单,但是@Frank的答案显示LLVM的std :: atomic实现在某些更高的位中进行异或运算所以你不要当对象被2的大功率分开时,t会自动获得混叠(这比任何其他随机排列更常见).

我认为(但我不确定)g ++和clang ++是ABI兼容的; 即他们使用相同的散列函数和表,因此他们同意哪个锁序列化访问哪个对象.但是,锁定都是在内部完成的libatomic,所以如果你动态链接,libatomic那么调用的同一个程序中的所有代码__atomic_store_16都将使用相同的实现; clang ++和g ++肯定会同意调用哪些函数名,这就足够了.(但请注意,只有不同进程之间共享内存中的无锁原子对象才能工作:每个进程都有自己的锁定哈希表.无锁对象应该(实际上是)只需在普通CPU的共享内存中工作体系结构,即使该区域映射到不同的地址.)

散列冲突意味着两个原子对象可能共享同一个锁.这不是一个正确性问题,但它可能是一个性能问题:您可以让所有4个线程争用访问任一对象,而不是两个线程分别相互竞争两个不同的对象.大概这是不寻常的,通常你的目标是你的原子对象在你关心的平台上无锁.但大多数时候你并没有真正走运,而且基本上没问题.

死锁是不可能的,因为没有任何std::atomic函数试图同时锁定两个对象.因此,获取锁的库代码永远不会尝试在持有其中一个锁的同时获取另一个锁.额外争用/序列化不是正确性问题,只是性能问题.


x86-64 GCC与MSVC的16字节对象:

作为一个黑客,编译器可以lock cmpxchg16b用来实现16字节的原子加载/存储,以及实际的读 - 修改 - 写操作.

这比锁定更好,但与8字节原子对象相比具有不良性能(例如纯负载与其他负载竞争).它是唯一一个以16字节1自动执行任何操作的安全方法.

AFAIK,MSVC从不使用lock cmpxchg16b16字节对象,它们基本上与24或32字节对象相同.

gcc6和更早版本lock cmpxchg16b在编译时内联-mcx16(不幸的是cmpxchg16b不是x86-64的基线;第一代AMD K8 CPU缺少它.)

gcc7决定始终调用libatomic并且永远不会将16字节对象报告为无锁,即使libatomic函数仍然可以lock cmpxchg16b在指令可用的机器上使用.升级到MacPorts gcc 7.3后,请参阅is_lock_free()返回false.解释此更改的gcc邮件列表消息在此处.

您可以使用union hack在x86-64上使用gcc/clang获得一个相当便宜的ABA指针+计数器:如何使用c ++ 11 CAS实现ABA计数器?. lock cmpxchg16b用于指针和计数器的更新,但mov只是指针的简单加载.这只适用于16字节对象实际上是无锁的情况lock cmpxchg16b.


脚注1: movdqa在一些(但不是全部)x86微体系结构中,16字节的加载/存储在实践中是原子的,并且没有可靠或记录的方法来检测它何时可用.请参阅为什么在x86上对自然对齐的变量进行整数赋值?和SSE指令:哪些CPU可以进行原子16B内存操作?例如,K10 Opteron只显示在具有HyperTransport的套接字之间的8B边界处撕裂.

因此编译器编写者必须小心谨慎,不能movdqa使用SSE2 movq在32位代码中使用8字节原子加载/存储的方式.如果CPU供应商可以记录某些微体系结构的某些保证,或者为原子16,32和64字节对齐的向量加载/存储(使用SSE,AVX和AVX512)添加CPUID功能位,那将是很好的.也许哪些主板供应商可以在使用特殊一致性胶水芯片的时髦多插槽机器上的固件中禁用,这些芯片不会原子地传输整个缓存线.



2> Frank..:

回答这些问题的最简单方法通常是查看生成的装配并从那里取出.

编译以下内容(我使你的结构更大,以躲避狡猾的编译器恶作剧):

#include 

struct foo {
    double a;
    double b;
    double c;
    double d;
    double e;
};

std::atomic var;

void bar()
{
    var.store(foo{1.0,2.0,1.0,2.0,1.0});
}

在clang 5.0.0中,在-O3下产生以下内容:请参阅godbolt

bar(): # @bar()
  sub rsp, 40
  movaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [1.000000e+00,2.000000e+00]
  movaps xmmword ptr [rsp], xmm0
  movaps xmmword ptr [rsp + 16], xmm0
  movabs rax, 4607182418800017408
  mov qword ptr [rsp + 32], rax
  mov rdx, rsp
  mov edi, 40
  mov esi, var
  mov ecx, 5
  call __atomic_store

很好,编译器委托给一个内在的(__atomic_store),这并没有告诉我们这里到底发生了什么.但是,由于编译器是开源的,我们可以很容易地找到内在的实现(我在https://github.com/llvm-mirror/compiler-rt/blob/master/lib/builtins/atomic.c中找到它)):

void __atomic_store_c(int size, void *dest, void *src, int model) {
#define LOCK_FREE_ACTION(type) \
    __c11_atomic_store((_Atomic(type)*)dest, *(type*)dest, model);\
    return;
  LOCK_FREE_CASES();
#undef LOCK_FREE_ACTION
  Lock *l = lock_for_pointer(dest);
  lock(l);
  memcpy(dest, src, size);
  unlock(l);
}

看起来神奇的事情发生了lock_for_pointer(),所以让我们来看看它:

static __inline Lock *lock_for_pointer(void *ptr) {
  intptr_t hash = (intptr_t)ptr;
  // Disregard the lowest 4 bits.  We want all values that may be part of the
  // same memory operation to hash to the same value and therefore use the same
  // lock.  
  hash >>= 4;
  // Use the next bits as the basis for the hash
  intptr_t low = hash & SPINLOCK_MASK;
  // Now use the high(er) set of bits to perturb the hash, so that we don't
  // get collisions from atomic fields in a single object
  hash >>= 16;
  hash ^= low;
  // Return a pointer to the word to use
  return locks + (hash & SPINLOCK_MASK);
}

这里是我们的解释:原子的地址用于生成一个哈希键来选择一个预先分配的锁.


@FrançoisAndrieux是的,我知道.我个人的偏好是在评论中使用godbolt链接,但实际上在写答案时复制粘贴结果,以便它们保持完全自包含(如果程序集足够短)

3> Hadi Brais..:

从C++标准的29.5.9开始:

注意:原子特化的表示不必与其对应的参数类型具有相同的大小.专业化应尽可能具有相同的大小,因为这减少了移植现有代码所需的工作量. - 结束说明

尽管不是必需的,但最好使原子的大小与其参数类型的大小相同.实现此目的的方法是避免锁定或将锁存储在单独的结构中.正如其他答案已经清楚解释的那样,哈希表用于保存所有锁.这是为使用中的所有原子对象存储任意数量的锁的最有效内存的方法.


不在每个对象中放置锁的另一个原因是与C11原子的互操作性,其中静态初始化是一个问题.C11标准定义了一个`ATOMIC_VAR_INIT`宏(它对复合类型不起作用),并且还要求原子对象的静态零初始化起作用.并且C11不提供任何析构函数来释放每个对象锁中的OS资源.有关C11标准中发现的问题的一些讨论,另请参阅https://developers.redhat.com/blog/2016/01/14/toward-a-better-use-of-c11-atomics-part-1/.
推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文比较了eBPF和WebAssembly作为云原生VM的特点和应用领域。eBPF作为运行在Linux内核中的轻量级代码执行沙箱,适用于网络或安全相关的任务;而WebAssembly作为图灵完备的语言,在商业应用中具有优势。同时,介绍了WebAssembly在Linux内核中运行的尝试以及基于LLVM的云原生WebAssembly编译器WasmEdge Runtime的案例,展示了WebAssembly作为原生应用程序的潜力。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
author-avatar
惠嘟du
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有