首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将指针浇铸到_Atomic指针和_Atomic大小

将指针浇铸到_Atomic指针和_Atomic大小
EN

Stack Overflow用户
提问于 2019-03-22 12:23:03
回答 2查看 915关注 0票数 6

根据我对标准的理解,不支持*(_Atomic TYPE*)&(TYPE){0} (换句话说,将指向非原子的指针转换为指向相应的原子和取消引用的指针)。

gcc和/或clang是否认为如果TYPE是/不是无锁的,那么它是一个扩展吗?(问题1)

第二个和相关的问题:我的印象是,如果TYPE不能实现为一个无锁原子,那么需要在相应的_Atomic TYPE中嵌入一个锁。但是,如果我把TYPE作为一个较大的结构,那么在clanggcc上,它的大小都与_Atomic TYPE相同。

这两个问题的代码:

代码语言:javascript
复制
#include <stdatomic.h>
#include <stdio.h>

#if STRUCT
typedef struct {
    int x;
    char bytes[50];
} TYPE;
#else
typedef int TYPE;
#endif

TYPE x;

void f (_Atomic TYPE *X)
{
    *X = (TYPE){0};
}

void use_f()
{
    f((_Atomic TYPE*)(&x));
}

#include <stdio.h>
int main()
{
    printf("%zu %zu\n", sizeof(TYPE), sizeof(_Atomic TYPE));
}

现在,如果我用-DSTRUCT编译上述代码片段,gcc和clang都将结构及其原子变体保持在相同的大小,并为存储生成一个名为__atomic_store的函数调用(通过与-latomic链接解决)。

如果没有锁嵌入到结构的_Atomic版本中,这是如何工作的?(问题2)

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-03-22 13:00:58

_Atomic在Clang的一些角落里改变了对齐方式,GCC也很有可能在未来得到修复(PR 65146)。在这些情况下,通过强制转换添加_Atomic不起作用(从C标准的角度来看,这很好,因为它是未定义的行为,正如您所指出的)。

如果对齐是正确的,则更适合使用__atomic内置程序,这些构建器正是为该用例设计的:

如前所述,如果ABI为普通(非原子)类型提供的对齐不足,并且_Atomic将更改对齐(目前只使用Clang ),则这将无法工作。

这些内置程序也可以在非原子类型的情况下工作,因为它们使用的是线外锁。这也是为什么使用相同机制的_Atomic类型不需要额外存储的原因。这意味着由于无意中共享锁,存在一些不必要的争用。这些锁是如何实现的,这是一个可能在未来版本的libatomic中发生变化的实现细节。

通常,对于具有涉及锁定的原子构建器的类型,将它们与共享或别名内存映射一起使用不起作用。这些内置程序也不是异步信号安全的。(所有这些特性在技术上都超出了C标准。)

票数 4
EN

Stack Overflow用户

发布于 2020-05-07 11:36:27

这种方法不是合法的C11,但我设法欺骗了我的编译器(Intel 2019),将原子类型和非原子“简单”类型转换为如下所示。

首先,我查看了我的系统(x86_64)中的stdatomic.h,以了解各种原子类型的实际定义是什么。就我所能看出的简单积分类型和指针而言,原子类型与普通类型是完全相同的,而且它们是显式的“无锁”。

下一步是使用sizeof()运算符来查看原子类型实际使用的字节数,我再次发现原子int为4字节,原子指针为8--正如我在64位系统上所预期的那样。

编译器禁止显式强制转换,但这起了作用:

代码语言:javascript
复制
typedef struct { void          *ptr; } IS_NORMAL;
typedef struct { atomic_address ptr; } IS_ATOMIC;

IS_NORMAL  a;
IS_ATOMIC *b = (IS_ATOMIC *)&a;

a.ptr = <address>
/* then inspection in the debugger shows that b->ptr is also <address> */

正如上面所示,它将允许我在这两种结构类型之间进行转换,并且当我在IS_ATOMIC指针变体上使用原子函数(例如IS_ATOMIC())时,调试器向我展示了非原子结构地址的内容更改为期望值。

这时你可能会问:“为什么要这样做?”答案是,我有一个多线程应用程序,在这个应用程序中,我希望将数据库记录锁定一段短时间,这样单个线程就可以在没有其他线程争用的情况下更新它,然后在完成时释放锁。历史上,我用一个关键部分来保护这个操作,但这是非常悲观的,因为我可能有--比如说-- 10,000,000条记录,并且随机更新它们,所以两个线程实际上试图更新相同记录的可能性很小,但是一个关键部分无条件地阻塞了所有线程。每个记录都由一个指针引用,因此进程:

  1. 原子地获取所需的记录指针,并将其替换为静态定义的“繁忙”指针。
  2. 检查它是否已经“忙”,如果是旋转和重试,直到我们得到“不忙”。
  3. 我们现在有了对此记录的唯一访问权限,所以更新它。
  4. 将“繁忙”指针替换为原始指针。

因此,步骤(1)锁和步骤(4)解锁,并且与关键区段方法不同,只有当两个线程试图访问相同的地址时,access才需要等待。它似乎可以工作,而且在我的6核心系统上(在超线程上,所以12个线程),它比在实际数据集上使用一个关键部分快5倍。

那么,为什么不首先将指向记录的指针定义为原子呢?答案是,这个特定的代码可能会在其他地方对该信息进行非线程访问,而且它还可能以一种已知的不可争用的方式进行线程访问;事实上,在大多数情况下,我不想使用锁定机制,因为它的成本很高。定时测试表明,一个典型的原子锁/解锁操作在我的系统上似乎要花费5到10纳秒,我想在不需要的时候避免这种开销,所以在那些情况下,我只使用原始指针。

我把这个作为解决这个问题的方法。我知道C11是不正确的,我知道它可能只适用于x86类型的体系结构--或者至少只适用于整数和指针类型是无锁且“本质上是原子的”的体系结构,而且我也承认,如果您知道如何在汇编程序中编写(我不知道),那么可能有更好的方法锁定给定的地址。我很高兴听到一个更好的解决办法。

顺便说一句,我还尝试了事务内存(即_xbegin() )。_xend()作为解决这个问题的一种方法。我发现它适用于一些小的测试问题,但是一旦我将其放大到实际数据,我就会经常遇到_xbegin()故障,我认为这是因为当您正在访问的地址不在缓存内存中时,它往往会退出,迫使您选择回退代码路径。英特尔对它的工作细节并不十分热心,所以这个解释可能是错误的。

我还研究过硬件锁省略作为加速关键区段方法的一种方法,但据我所见,由于易受黑客攻击,所以不推荐使用它。不管怎么说,我太笨了,不知道怎么用它!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55299525

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档