我读了this post。
有趣的是,答案指出:
实际上您确实需要修改代码以不使用C库函数 在易失性缓冲区上。您的选择包括:
- 编写与易失性缓冲区一起使用的C库函数的替代方法。
- 使用适当的内存屏障。
我很好奇#2怎么可能。假设有2个(单线程)进程使用shm_open()
+ memcpy()
在CentOS 7上创建/打开相同的共享内存。
我正在x86-64上使用gcc / g ++ 7。
答案 0 :(得分:2)
要直接回答您的紧迫问题:使用标准内存屏障-将while循环更改为:
while (strncmp((char *) mem, "exit", 4) != 0)
atomic_thread_fence(memory_order_acquire);
(请注意,这是C。您已将问题标记为C ++,而您所引用的原始文章是C。但是,等效的C ++看起来非常相似。)
粗略地说,memory_order_acquire
意味着您希望查看其他线程(或本例中的其他进程)所做的更改。对于当前的编译器,在我进行的一些简单实验中,这似乎已经足够了,但是在没有原子操作的情况下,技术上可能还不够。完整的解决方案将使用原子负载重新实现strncmp
函数。
严格来说,您不应该在易失性缓冲区上使用strncmp
之类的东西(即使存在内存障碍,这几乎肯定会引起未定义的行为,尽管我想您对当前的编译器永远不会有问题)
还有很多更好的方法可以解决您链接的文章中描述的问题。尤其是在这种情况下,首先使用共享内存几乎没有意义。简单的管道将是更好的解决方案。
答案 1 :(得分:2)
滚动您自己的编译器内存障碍,以告知编译器所有全局变量可能已被异步修改。
在C ++ 11和更高版本中,该语言定义了一个内存模型,该模型指定在非原子变量上进行的数据竞争是未定义的行为。因此,尽管这在现代编译器上仍然可行,但我们可能只应该谈论C ++ 03和更早版本。在C ++ 11之前,您必须自己滚动或使用pthreads库函数或任何其他库。
相关:How does a mutex lock and unlock functions prevents CPU reordering?
在GNU C asm("" ::: "memory")
中是编译器的内存障碍。在x86(一种强序体系结构)上,这本身就为您提供了acq_rel语义,因为x86唯一的运行时重排序方法是StoreLoad。
优化器将其视为对非内联函数的函数调用:该函数之外的任何东西可能具有指针的任何内存都被假定为已修改。参见Understanding volatile asm vs volatile variable。 (没有输出的GNU C扩展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");
}
}
用于x86-64(on the Godbolt compiler explorer)的gcc -O3将此编译为看起来像源代码的asm,而无需增加负载:
# 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()
循环来避免这种情况。 (Why are loops always compiled into "do...while" style (tail jump)?)。
除此之外,它与我们使用std::atomic_int
或volatile
获得的asm相同。 (请参阅Godbolt链接。)
在没有障碍物的情况下,它确实可以提升负载:
# 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或其他任何文件。 (并且有运行时开销)。
答案 2 :(得分:-1)
您可以使用a process-shared mutex.或信号灯。
NAME
pthread_mutexattr_getpshared
,pthread_mutexattr_setpshared
-获取 并设置进程共享属性简介
#include <pthread.h> int pthread_mutexattr_getpshared(const pthread_mutexattr_t * restrict attr, int *restrict pshared); int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); [Option End]
描述
pthread_mutexattr_getpshared()
函数应获取该值 引用的属性对象中的流程共享属性 由attr
。pthread_mutexattr_setpshared()
函数应设置 初始化属性对象中的进程共享属性 由attr
引用。进程共享属性设置为
PTHREAD_PROCESS_SHARED
允许任何有权访问的线程对互斥量进行操作 分配互斥锁的内存,即使互斥锁为 分配在由多个进程共享的内存中。如果 进程共享属性为PTHREAD_PROCESS_PRIVATE
,互斥体应为 只能由与 初始化互斥量的线程;如果线程不同 进程尝试在这种互斥锁上进行操作,其行为是 未定义。该属性的默认值应为PTHREAD_PROCESS_PRIVATE
。
有关进程共享互斥锁的示例,请参见Condition Variable in Shared Memory - is this code POSIX-conformant?。
NAME
sem_init
-初始化未命名的信号灯(REALTIME)简介
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned value); [Option 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()
操作。
有关进程共享信号灯的示例,请参见How to share semaphores between processes using shared memory。