Я снова и снова читал об атомных типах и операциях boost и std (c ++ 11), и все же я не уверен, что понимаю это правильно (а в некоторых случаях я вообще этого не понимаю). Итак, у меня есть несколько вопросов по этому поводу.
Мои источники, которые я использую для обучения:
- Документация по Boost: http://www.boost.org/doc/libs/1_53_0/doc/html/atomic.html
- http://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/
Рассмотрим следующий фрагмент:
atomic<bool> x,y;
void write_x_then_y()
{
x.store(true, memory_order_relaxed);
y.store(true, memory_order_release);
}
# 1: Это эквивалентно следующему?
atomic<bool> x,y;
void write_x_then_y()
{
x.store(true, memory_order_relaxed);
atomic_thread_fence(memory_order_release); // *1
y.store(true, memory_order_relaxed); // *2
}
# 2: Верно ли следующее утверждение?
Строка * 1 гарантирует, что когда операции, выполняемые в этой строке (например, * 2), видны (для другого потока, использующего получение), код выше * 1 также будет виден (с новыми значениями).
Следующий фрагмент расширяет предыдущие:
void read_y_then_x()
{
if(y.load(memory_order_acquire))
{
assert(x.load(memory_order_relaxed));
}
}
# 3: Это эквивалентно следующему?
void read_y_then_x()
{
atomic_thread_fence(memory_order_acquire); // *3
if(y.load(memory_order_relaxed)) // *4
{
assert(x.load(memory_order_relaxed)); // *5
}
}
# 4: Верны ли следующие утверждения?
- Строка * 3 гарантирует, что если видны некоторые операции в порядке деблокирования (в другом потоке, например, * 2), каждая операция выше порядка деблокирования (например, * 1) также будет видна.
- Это означает, что assert at * 5 никогда не завершится ошибкой (со значениями по умолчанию false).
- Но это не гарантирует, что даже если физически (в процессоре) * 2 происходит раньше, чем * 3, это будет видно по фрагменту выше (выполняется в другом потоке) - функция read_y_then_x () все еще может читать старые значения. Единственное, что можно гарантировать, это то, что если y истинно, x также будет истинным.
# 5: Увеличение (операция добавления 1) к атомарному целому числу может быть memory_order_relaxed, и никакие данные не будут потеряны. Единственная проблема - это порядок и время видимости результата.
В соответствии с повышением, следующий сокращенный - это рабочий счетчик ссылок:
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
# 6 Почему для уменьшения используется memory_order_release? Как это работает (в контексте)? Если то, что я написал ранее, верно, что делает возвращаемое значение самым последним, особенно когда мы используем получение ПОСЛЕ чтения, а не до / во время?
# 7 Почему после обнуления счетчика ссылок появляется заказ на приобретение? Мы просто читаем, что счетчик равен нулю и не используется никакая другая атомарная переменная (сам указатель не помечен / не используется как таковой).