Видимость данных между потоками без блокировки

Я понимаю основные правила упорядочения памяти в 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 объекты соответствуют требованиям).

Затем вы более или менее получаете поведение, которое вы описываете в своем вопросе, когда второй поток может увидеть состояние "наполовину готово" (некоторые объекты обновлены, другие нет). Без синхронизации второй поток действительно не может знать, когда выполняются обновления первым потоком, но, по крайней мере, безопасно одновременно выполнять чтение / запись в свободные от гонки данных объекты.

Другие вопросы по тегам