Видимость данных между потоками без блокировки
Я понимаю основные правила упорядочения памяти в C++11, особенно порядок упорядочения при получении. У меня есть большой кусок памяти, разделенный между двумя потоками, где мне не нужна атомарность, но я хочу убедиться, что в конечном итоге все изменения, сделанные одним потоком, будут видны в другом, особенно на платформах с расслабленной моделью памяти.
Можно ли просто использовать атомную защиту var только для запуска синхронизации памяти? Например,
std::atomic<bool> guardVar;
char *shared_mem=get_shared_mem();
(thread 1)
while(true) {
happens_many_things();
do_whatever_I_want_with_shared_mem();
guardVar.store(0, std::memory_order_release);
}
(in thread 2)
while(true) {
guardVar.load(std::memory_order_acquire);
read_shared_mem_no_problem_if_inconsistent();
}
Опять же, это не проблема, если поток 2 читает состояние "наполовину готово" в середине do_whwhat_I_want_with_shared_mem(), я просто хочу убедиться, что я получаю все изменения, записанные потоком 1 после хорошо определенной точки.
Основываясь на этой статье, она должна работать, но я не вижу подобных решений в сети, и нелегко проверить, действительно ли она выполняет то, что я намереваюсь.
Это нормально? Если это так, есть ли более элегантный способ?
1 ответ
это не проблема, если поток 2 читает состояние "наполовину готово" в середине do_whwhat_I_want_with_shared_mem()
Это ошибка, вы не можете получить доступ к общей памяти несколькими потоками, если один из них изменяет данные. Стандарт C++ называет это гонкой данных, и это ведет к неопределенному поведению.
Доступ между двумя потоками должен быть синхронизирован, но способ, которым вы используете std::atomic
это неверно. store_release
в потоке 1 немедленно следует доступ к тем же данным снова. То же самое для load_acquire
; нет синхронизации между этими двумя операциями, и, следовательно, вы имеете дело с гонкой данных.
Чтобы гарантировать, что ваша общая память доступна только одному потоку за раз, guardVar
технически можно использовать так:
std::atomic<bool> guardVar{false};
(thread 1)
while(true) {
while (guardVar.exchange(true, std::memory_order_acquire)); // LOCK
happens_many_things();
do_whatever_I_want_with_shared_mem();
guardVar.store(false, std::memory_order_release); // UNLOCK
}
(in thread 2)
while(true) {
while (guardVar.exchange(true, std::memory_order_acquire)); // LOCK
read_shared_mem_no_problem_if_inconsistent();
guardVar.store(false, std::memory_order_release); // UNLOCK
}
Но так как это использует std::atomic
как мьютекс довольно неэффективным способом (обратите внимание на вращение), вы действительно должны использовать std::mutex
Обновить:
Все еще возможно использовать вашу разделяемую память без блокировки, но тогда вы несете ответственность за то, чтобы каждый отдельный объект, к которому осуществляется доступ в разделяемой памяти, был свободен от гонки данных (std::atomic
объекты соответствуют требованиям).
Затем вы более или менее получаете поведение, которое вы описываете в своем вопросе, когда второй поток может увидеть состояние "наполовину готово" (некоторые объекты обновлены, другие нет). Без синхронизации второй поток действительно не может знать, когда выполняются обновления первым потоком, но, по крайней мере, безопасно одновременно выполнять чтение / запись в свободные от гонки данных объекты.