std::atomic_signal_fence
在阅读 cpp11-on-multicore/common/inmemorylogger.h 时,发现了其中有这样的用法:
void log(const char* msg, size_t param = 0)
{
std::atomic_signal_fence(std::memory_order_seq_cst); // Compiler barrier
// On weak CPUs and current C++ compilers, memory_order_consume costs the same as acquire. :(
// (If you don't like that, you can probably demote this load to relaxed and get away with it.
// Technically, you'd be violating the spec, but in practice it will likely work. Just
// inspect the assembly and make sure there is a data dependency between m_tail.load and
// both subsequent uses of page, and you're golden. The only way I can imagine the dependency
// chain being broken is if the compiler knows the addresses that will be allocated
// in allocateEventFromNewPage at runtime, which is a huuuuuuuuuge leap of the imagination.)
// http://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11
Page* page = m_tail.load(std::memory_order_consume);
Event* evt;
int index = page->index.fetch_add(1, std::memory_order_relaxed);
if (index < EVENTS_PER_PAGE)
evt = &page->events[index];
else
evt = allocateEventFromNewPage(); // Double-checked locking is performed inside here.
evt->tid = std::this_thread::get_id();
evt->msg = msg;
evt->param = param;
std::atomic_signal_fence(std::memory_order_seq_cst); // Compiler barrier
}
cppreference std::atomic_signal_fence
extern "C" void atomic_signal_fence( std::memory_order order ) noexcept;
Establishes memory synchronization ordering of non-atomic and relaxed atomic accesses, as instructed by order
, between a thread and a signal handler executed on the same thread.
NOTE: 这段话的意思是: 按照参数
order
所指示的,在 "a thread" 和 "a signal handler executed on the same thread" 之间建立"memory synchronization ordering of non-atomic and relaxed atomic accesses"
This is equivalent to std::atomic_thread_fence, except no CPU instructions for memory ordering are issued. Only reordering of the instructions by the compiler is suppressed(抑制) as order
instructs. For example, a fence with release semantics prevents reads or writes from being moved past subsequent writes and a fence with acquire semantics prevents reads or writes from being moved ahead of preceding reads.
NOTE: 显然,std::atomic_signal_fence 是作为 compiler memory fence/barrier的
stackoverflow How to correctly use std::atomic_signal_fence()?
cppreference.com documents this function as "fence between a thread and a signal handler executed in the same thread". But I found no example on the Internet.
I wonder whether or not the following psuedo-code correctly illustrates the function of std::atomic_signal_fence()
:
int n = 0;
SignalObject s;
void thread_1()
{
s.wait();
std::atomic_signal_fence(std::memory_order_acquire);
assert(1 == n); // never fires ???
}
void thread_2()
{
n = 1;
s.signal();
}
int main()
{
std::thread t1(thread_1);
std::thread t2(thread_2);
t1.join(); t2.join();
}
comments
A signal_fence
is a "compiler" barrier: it prevents compile-time reordering/combining/hoisting of memory operations, but will never emit a hardware memory barrier instructions. Jeff Preshing's blog is excellent at explaining this stuff, definitely a must-read if you're unsure about memory-ordering stuff. – Peter Cordes Feb
A
No, your code does not demonstrate correct usage of atomic_signal_fence
. As you quote cppreference.com, atomic_signal_fence
only perform synchronization between a signal handler and other code running on the same thread. That means that it does not perform synchronization between two different threads. Your example code shows two different threads.
The C++ spec contains the following notes about this function:
Note: compiler optimizations and reorderings of loads and stores are inhibited in the same way as with
atomic_thread_fence
, but the hardware fence instructions that atomic_thread_fence would have inserted are not emitted.Note:
atomic_signal_fence
can be used to specify the order in which actions performed by the thread become visible to the signal handler.
Here's an example of correct, if not motivating, usage:
#include <atomic>
#include <cassert> // asserta
#include <csignal> // std::signal
#include <cstdlib>//std::exit
static_assert(2 == ATOMIC_INT_LOCK_FREE, "this implementation does not guarantee that std::atomic<int> is always lock free.");
std::atomic<int> a { 0 };
std::atomic<int> b { 0 };
extern "C" void handler(int)
{
if (1 == a.load(std::memory_order_relaxed))
{
std::atomic_signal_fence(std::memory_order_acquire);
assert(1 == b.load(std::memory_order_relaxed));
}
std::exit(0);
}
int main()
{
std::signal(SIGTERM, &handler);
b.store(1, std::memory_order_relaxed);
std::atomic_signal_fence(std::memory_order_release);
a.store(1, std::memory_order_relaxed);
}
// g++ --std=c++11 test.cpp
The assertion, if encountered, is guaranteed to hold true.