首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >访问共享内存没有易失性,std::原子,信号量,互斥和自旋锁?

访问共享内存没有易失性,std::原子,信号量,互斥和自旋锁?
EN

Stack Overflow用户
提问于 2018-07-10 11:59:36
回答 3查看 1.4K关注 0票数 1

我读过这个职位

答案有趣地指出:

实际上,您需要修改代码以避免在易失性缓冲区上使用C库函数。你的选择包括:

  1. 为使用易失性缓冲区的C库函数编写自己的替代方案。
  2. 使用适当的记忆屏障。

我很好奇为什么#2是可能的。假设2(单线程)进程使用shm_open() + memcpy()在CentOS 7上创建/打开相同的共享内存,我使用gcc/g++ 7和x86-64。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-07-10 13:23:47

滚动您自己的编译器内存屏障,告诉编译器所有的全局变量都可能是异步修改的。

在C++11和更高版本中,该语言定义了一个内存模型,该模型指定非原子变量上的数据竞争是未定义的行为。因此,虽然这在现代编译器的实践中仍然有效,但我们可能应该只讨论C++03和更早的版本。在C++11之前,您必须滚动自己的函数,或者使用p线程库函数或任何其他库。

相关:互斥锁和解锁功能如何防止CPU重新排序?

GNU asm("" ::: "memory") 中的是一个编译器内存屏障, On x86是一种强有序的体系结构,它本身就给出了acq_rel语义,因为x86所能做的唯一一种运行时重排序功能就是StoreLoad。

优化器将其完全视为对非内联函数的函数调用:假定在此函数之外的任何内存都可能有指针被修改。见了解易失性asm与易失性变量。(没有输出的GNU扩展asm语句是隐式volatile,因此asm volatile("" ::: "memory")更显式,但等价。)

有关编译器障碍的更多信息,请参见http://preshing.com/20120625/memory-ordering-at-compile-time/。但是请注意,这不仅仅是阻塞重新排序,而是阻塞优化,比如将值保存在循环中的寄存器中。

例如,像while(shared_var) {}这样的自旋循环可以编译成if(shared_var) infinite_loop;,但是通过设置一个屏障,我们可以防止这样的情况发生:

代码语言:javascript
复制
void spinwait(int *ptr_to_shmem) {
    while(shared_var) {
        asm("" ::: "memory");
    }
}

gcc -O3用于x86-64 ( void+spinwait(std::atomic_int+*ptr_to_shmem)+{ ++++while(*ptr_to_shmem)+{} }'),l:'5',n:'0',o:'C+++source+#1',t:'0')),k:31.03335155822854,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:clang600,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c++,libs:!(),options:'-Wall+-O3',source:1),l:'5',n:'0',o:'x86-64+clang+6.0.0+(Editor+#1,+Compiler+#1)+C++',t:'0')),header:(),k:34.48332422088573,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g81,filters:(b:'0',binary:'1',commentOnly:'1',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c++,libs:!(),options:'-Wall+-O3',source:1),l:'5',n:'0',o:'x86-64+gcc+8.1+(Editor+#1,+Compiler+#2)+C++',t:'0')),header:(),k:34.48332422088573,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)">关于戈德波特编译器浏览器将其编译为类似于源代码的asm,而不需要从循环中提升负载:

代码语言:javascript
复制
# gcc's output
spinwait(int*):
    jmp     .L5           # gcc doesn't check or know that the asm statement is empty
.L3:
#APP
# 3 "/tmp/compiler-explorer-compiler118610-54-z1284x.occil/example.cpp" 1
        #asm comment: barrier here
# 0 "" 2
#NO_APP
.L5:
    mov     eax, DWORD PTR [rdi]
    test    eax, eax
    jne     .L3
    ret

asm语句仍然是一个易失性的asm语句,它必须运行与循环体在C抽象机器中运行的次数相同的次数。GCC跳过空asm语句,到达循环底部的条件,以确保在运行(空) asm语句之前检查条件。我在asm模板中放置了一个asm注释,以查看整个功能在编译器生成的asm中的结束位置。我们本可以通过在C源代码中编写一个do{}while()循环来避免这种情况。(为什么循环总是被编译成"do...while“样式(尾跳)?)。

除此之外,它与我们从使用std::atomic_intvolatile获得的asm是一样的。(见“天栓”链接)。

没有障碍物的,它确实提升了负载:

代码语言:javascript
复制
# clang6.0 -O3
spinwait_nobarrier(int*):               # @spinwait_nobarrier(int*)
        cmp     dword ptr [rdi], 0
        je      .LBB1_2

.LBB1_1:                     #infinite loop
        jmp     .LBB1_1

.LBB1_2:                     # jump target for 0 on entry
        ret

如果没有任何特定于编译器的内容,您实际上可以使用一个非内联函数来击败优化器,但是您可能必须将其放到库中才能击败链接时间优化。仅仅是另一个源文件是不够的。所以你最终需要一个特定于系统的Makefile或者什么的。(它具有运行时开销)。

票数 3
EN

Stack Overflow用户

发布于 2018-07-10 13:20:15

要直接回答您的直接问题:使用标准内存屏障-将while循环更改为:

代码语言:javascript
复制
while (strncmp((char *) mem, "exit", 4) != 0)
    atomic_thread_fence(memory_order_acquire);

(请注意,这是C。您已经将您的问题标记为C++,而您所指的最初的帖子是C。然而,等效的C++看起来非常相似)。

粗略地说,memory_order_acquire意味着您希望看到其他线程(或者在本例中是其他进程)所做的更改。在我进行的一些简单实验中,使用当前的编译器似乎就足够了,但是在技术上,如果没有原子操作,可能不是足够的。一个完整的解决方案将使用原子负载重新实现strncmp函数。

严格地说,您不应该在易失性缓冲区上使用strncmp等(即使存在内存屏障,这几乎肯定会引发未定义的行为,尽管我认为当前编译器不会有问题)。

此外,还有更好的方法来解决您链接的帖子中描述的问题。特别是在这种情况下,首先使用共享内存是没有意义的;简单的管道将是一个更好的解决方案。

票数 2
EN

Stack Overflow用户

发布于 2018-07-10 12:10:03

您可以使用进程共享互斥体。或信号量。

名称 pthread_mutexattr_getpsharedpthread_mutexattr_setpshared -获取并设置进程共享属性。 简要说明 #包括pthread_mutexattr_setpshared(pthread_mutexattr_t *attr(const *pthread_mutexattr_getpshared,int *pthread_mutexattr_getpshared,int *pthread_mutexattr_getpshared);int pthread_mutexattr_t *attr,int *pthread_mutexattr_getpshared;选项结束 描述 pthread_mutexattr_getpshared()函数将从attr引用的属性对象中获取进程共享属性的值。pthread_mutexattr_setpshared()函数将在attr引用的初始化属性对象中设置进程共享属性。 进程共享属性设置为PTHREAD_PROCESS_SHARED,允许访问分配互斥对象的内存的任何线程对互斥对象进行操作,即使互斥锁是在由多个进程共享的内存中分配的。如果进程共享属性为PTHREAD_PROCESS_PRIVATE,则互斥只能由与初始化互斥线程在同一进程内创建的线程操作;如果不同进程的线程试图在此互斥对象上操作,则行为未定义。属性的默认值应为PTHREAD_PROCESS_PRIVATE

有关进程共享互斥的示例,请参见共享内存中的条件变量--这段代码符合POSIX吗?

对于进程共享信号量

名称 sem_init -初始化未命名的信号量(实时) 简要说明 #包括 int sem_init(sem_t *sem,int,unsigned );选项End 描述 sem_init()函数将初始化sem引用的未命名信号量。初始化信号量的值应为值。在成功调用sem_init()之后,信号量可用于随后对sem_wait()sem_timedwait()sem_trywait()sem_post()sem_destroy()的调用。这个信号量将保持可用,直到信号量被摧毁为止。 如果pshared参数有一个非零值,那么信号量在进程之间是共享的;在这种情况下,任何可以访问信号量sem的进程都可以使用sem来执行sem_wait()sem_timedwait()sem_trywait()sem_post()sem_destroy()操作。

有关进程共享信号量的示例,请参阅如何使用共享内存在进程之间共享信号量

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

https://stackoverflow.com/questions/51264628

复制
相关文章

相似问题

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