Я пытаюсь найти свои ноги в программировании без блокировки. Прочитав разные объяснения семантики упорядочения памяти, я хотел бы прояснить, какое возможное переупорядочение может произойти. Насколько я понял, инструкции могут быть переупорядочены компилятором (из-за оптимизации при компиляции программы) и процессором (во время выполнения?).
Для упрощенной семантики ссылка на cpp приводит следующий пример:
// Thread 1:
r1 = y.load(memory_order_relaxed); // A
x.store(r1, memory_order_relaxed); // B
// Thread 2:
r2 = x.load(memory_order_relaxed); // C
y.store(42, memory_order_relaxed); // D
Говорят, что если x и y изначально равны нулю, код может выдать r1 == r2 == 42, потому что, хотя A упорядочено перед B в потоке 1, а C упорядочено перед D в потоке 2, ничто не препятствует появлению D. перед A в порядке изменения y, а B от появления перед C в порядке изменения x. Как это могло случиться? Означает ли это, что C и D переупорядочиваются, поэтому порядок выполнения будет DABC? Можно ли изменить порядок A и B?
Для семантики получения-освобождения есть следующий пример кода:
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
Мне интересно, что, если бы мы использовали ослабленный порядок памяти вместо получения? Я предполагаю, что значение data
может быть прочитано до p2 = ptr.load(std::memory_order_relaxed)
, но как насчет p2
?
Наконец, почему в этом случае можно использовать расслабленный порядок памяти?
template<typename T>
class stack
{
std::atomic<node<T>*> head;
public:
void push(const T& data)
{
node<T>* new_node = new node<T>(data);
// put the current value of head into new_node->next
new_node->next = head.load(std::memory_order_relaxed);
// now make new_node the new head, but if the head
// is no longer what's stored in new_node->next
// (some other thread must have inserted a node just now)
// then put that new head into new_node->next and try again
while(!head.compare_exchange_weak(new_node->next, new_node,
std::memory_order_release,
std::memory_order_relaxed))
; // the body of the loop is empty
}
};
Я имею в виду и head.load(std::memory_order_relaxed)
, и head.compare_exchange_weak(new_node->next, new_node, std::memory_order_release, std::memory_order_relaxed)
.
Подводя итог всему вышесказанному, мой вопрос, по сути, заключается в том, когда мне нужно заботиться о потенциальном изменении порядка, а когда нет?
int data
будет прочитан без гарантии синхронизации с, которая не позволяет потоку чтения просмотреть его, пока он все еще записывается. Это была бы гонка данных типа UB, поскольку это не тип std::atomic.std::atomic<int>
использованиеmemory_order_relaxed
позволит избежать UBdata
и просто оставит вам действующую (но менее полезную) программу с обычной ошибкой гонки данных. Но сравнение строк после разыменованияptr
все равно будет гонкой данных, потому чтоstd::string
не является атомарным/свободным от блокировки контейнером. - person Peter Cordes   schedule 28.11.2016p2
. Затем мы сравниваем строку. Из-за правила не нарушать однопоточный код этот порядок должен быть сохранен. Как только мы получим что-то вp2
, мы гарантированно увидим указанные данные (как вы упомянули, на всех процессорах, кроме Alpha). Данные должны быть не менее новыми, чем указатель, поэтому, если указатель инициализирован, данные также должны быть уже инициализированы. Или я вас неправильно понял? - person mentalmushroom   schedule 28.11.2016memory_order_relaxed
, потому что любой из них позволит считывать указатель как не NULL, пока данные в классеstd::string
все еще записываются. (Обратите внимание, что компиляция для процессора, отличного от Alpha, не имеет значения в отношении UB или нет. Точно так же, как переполнение целого числа со знаком является UB, даже при компиляции для машины с дополнением до 2, где она имеет очень четко определенную семантику.) - person Peter Cordes   schedule 28.11.2016