gpt4 book ai didi

c - C11中的无锁乒乓球

转载 作者:行者123 更新时间:2023-12-03 12:54:07 24 4
gpt4 key购买 nike

我对C并发还很陌生,并试图让一些基础人员了解它的工作原理。

我想写一个符合标准的无锁乒乓实现,即一个线程打印 ping ,然后另一个线程打印 pong 并使其无锁。这是我的尝试:

#if ATOMIC_INT_LOCK_FREE != 2
#error atomic int should be always lock-free
#else
static _Atomic int flag;
#endif

static void *ping(void *ignored){
while(1){
int val = atomic_load_explicit(&flag, memory_order_acquire);
if(val){
printf("ping\n");
atomic_store_explicit(&flag, !val, memory_order_release);
}
}
return NULL;
}
static void *pong(void *ignored){
while(1){
int val = atomic_load_explicit(&flag, memory_order_acquire);
if(!val){
printf("pong\n");
atomic_store_explicit(&flag, !val, memory_order_release);
}
}
return NULL;
}

int main(int args, const char *argv[]){
pthread_t pthread_ping;
pthread_create(&pthread_ping, NULL, &ping, NULL);

pthread_t pthread_pong;
pthread_create(&pthread_pong, NULL, &pong, NULL);
}

我对其进行了几次测试,并且可以正常工作,但是有些事情看起来很奇怪:
  • 它是无锁的或不编译

  • 由于标准将无锁属性定义为等于2,因此原子类型上的所有操作始终都是无锁的。特别是我检查了编译代码,它看起来像
    sub    $0x8,%rsp
    nopl 0x0(%rax)
    mov 0x20104e(%rip),%eax # 0x20202c <flag>
    test %eax,%eax
    je 0xfd8 <ping+8>
    lea 0xd0(%rip),%rdi # 0x10b9
    callq 0xbc0 <puts@plt>
    movl $0x0,0x201034(%rip) # 0x20202c <flag>
    jmp 0xfd8 <ping+8>

    这似乎没问题,而且我们甚至不需要栅栏,因为 Intel CPU不允许使用较早的加载对商店进行重新排序。这种假设仅在我们知道硬件存储器模型不可移植的情况下才有效
  • 将stdatomics与pthreads一起使用

  • 我被glibc 2.27困住了,但尚未实现 threads.h。问题是这样做是否严格遵守?无论如何,如果我们有原子但没有线程,这有点奇怪。那么 stdatomic在多线程应用程序中的一致性用法是什么?

    最佳答案

    术语“无锁”有两种含义:

  • 计算机科学的含义:一个线程被阻塞不会阻止其他线程。 此任务无法使无锁,您需要线程彼此等待。 (https://en.wikipedia.org/wiki/Non-blocking_algorithm)
  • 使用无锁原子。您基本上是在创建自己的用于创建线程块的机制,在一个讨厌的自旋循环中等待,没有回退,最终放弃了CPU。

  • 各个stdatomic加载和存储操作分别是无锁的,但是您正在使用它们来创建某种2线程锁。

    您的尝试对我来说似乎是正确的。我看不到线程可以“错过”更新的方法,因为另一个线程要等到该线程完成后才能写另一个。而且我看不到两个线程都同时进入其关键部分的方法。
    一个更有趣的测试是使用解锁的stdio操作,例如
    fputs_unlocked("ping\n", stdio); 以利用(并依赖于)您已经保证线程之间相互排斥的事实。参见 unlocked_stdio(3)
    并将输出重定向到文件进行测试,以便stdio处于完全缓冲状态,而不是行缓冲状态。 (像 write()这样的系统调用无论如何都已完全序列化,例如 atomic_thread_fence(mo_seq_cst)。)

    It either lock-free or does not compile


    好吧,那为什么很奇怪?您选择这样做。这不是必需的;即使没有始终无锁的 atomic_int,该算法仍然可以在C实现上使用。 atomic_bool可能是一个更好的选择,它可以在更多平台上无锁,包括 int需要2个寄存器的8位平台(因为它必须至少为16位)。在效率更高的平台上,可以免费实现将 atomic_bool设置为4字节类型,但实际上可以使用IDK。 (在某些非x86平台上,字节加载/存储会花费额外的延迟周期来读取/写入高速缓存。在这里可以忽略不计,因为您始终要处理内核间高速缓存未命中的情况。)
    您可能会认为 atomic_flag 将是正确的选择,但它仅提供测试设置和清晰的RMW操作。不能简单地加载或存储。

    Such assumptions works only in case we know the hardware memory model which is not portable


    是的,但是这种无障碍的asm代码生成只会在针对x86进行编译时发生。编译器可以并且应该应用as-if规则来创建在编译目标上运行的asm,就像C源代码在C抽象机上运行一样。

    Using stdatomics with pthreads

    Does the ISO C Standard guarantee the atomic's behavior to be well-defined with all threading implementations (like pthreads, earlier LinuxThreads, etc...)


    不,ISO C对于诸如POSIX之类的语言扩展没什么好说的。
    它确实在脚注中(不是规范性的)说,无锁原子应该是无地址的,因此它们可以在访问同一共享内存的不同进程之间工作。 (或者,此脚注仅在ISO C++中,我没有去重新检查)。
    这是我想到ISO C或C++尝试规定扩展行为的唯一情况。
    但是POSIX标准有望说明一些有关stdatomic的信息!那是你应该看的地方;它扩展了ISO C,而不是相反,因此pthreads是必须指定其线程像C11 thread.h一样工作并且原子可以工作的标准。
    当然,实际上,对于所有线程共享相同虚拟地址空间的任何线程实现,stdatomic都可以100%优良。 这包括诸如 _Atomic my_large_struct foo;之类的非锁定内容。

    关于c - C11中的无锁乒乓球,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55616648/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com