我读过这个职位。
答案有趣地指出:
实际上,您需要修改代码以避免在易失性缓冲区上使用C库函数。你的选择包括:
我很好奇为什么#2是可能的。假设2(单线程)进程使用shm_open() + memcpy()在CentOS 7上创建/打开相同的共享内存,我使用gcc/g++ 7和x86-64。
发布于 2018-07-10 13:23:47
滚动您自己的编译器内存屏障,告诉编译器所有的全局变量都可能是异步修改的。
在C++11和更高版本中,该语言定义了一个内存模型,该模型指定非原子变量上的数据竞争是未定义的行为。因此,虽然这在现代编译器的实践中仍然有效,但我们可能应该只讨论C++03和更早的版本。在C++11之前,您必须滚动自己的函数,或者使用p线程库函数或任何其他库。
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;,但是通过设置一个屏障,我们可以防止这样的情况发生:
void spinwait(int *ptr_to_shmem) {
while(shared_var) {
asm("" ::: "memory");
}
}# 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
retasm语句仍然是一个易失性的asm语句,它必须运行与循环体在C抽象机器中运行的次数相同的次数。GCC跳过空asm语句,到达循环底部的条件,以确保在运行(空) asm语句之前检查条件。我在asm模板中放置了一个asm注释,以查看整个功能在编译器生成的asm中的结束位置。我们本可以通过在C源代码中编写一个do{}while()循环来避免这种情况。(为什么循环总是被编译成"do...while“样式(尾跳)?)。
除此之外,它与我们从使用std::atomic_int或volatile获得的asm是一样的。(见“天栓”链接)。
没有障碍物的,它确实提升了负载:
# 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或者什么的。(它具有运行时开销)。
发布于 2018-07-10 13:20:15
要直接回答您的直接问题:使用标准内存屏障-将while循环更改为:
while (strncmp((char *) mem, "exit", 4) != 0)
atomic_thread_fence(memory_order_acquire);(请注意,这是C。您已经将您的问题标记为C++,而您所指的最初的帖子是C。然而,等效的C++看起来非常相似)。
粗略地说,memory_order_acquire意味着您希望看到其他线程(或者在本例中是其他进程)所做的更改。在我进行的一些简单实验中,使用当前的编译器似乎就足够了,但是在技术上,如果没有原子操作,可能不是足够的。一个完整的解决方案将使用原子负载重新实现strncmp函数。
严格地说,您不应该在易失性缓冲区上使用strncmp等(即使存在内存屏障,这几乎肯定会引发未定义的行为,尽管我认为当前编译器不会有问题)。
此外,还有更好的方法来解决您链接的帖子中描述的问题。特别是在这种情况下,首先使用共享内存是没有意义的;简单的管道将是一个更好的解决方案。
发布于 2018-07-10 12:10:03
您可以使用进程共享互斥体。或信号量。
名称
pthread_mutexattr_getpshared,pthread_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()操作。
有关进程共享信号量的示例,请参阅如何使用共享内存在进程之间共享信号量。
https://stackoverflow.com/questions/51264628
复制相似问题