Каковы некоторые варианты использования для memory_order_relaxed

Модель памяти C++ имеет ослабленную атомарность, которая не дает никаких гарантий упорядочения операций с памятью. Кроме примера почтового ящика в C, который я нашел здесь:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1525.htm

Основываясь на мотивирующем примере в этой статье:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2153.pdf

Мне было любопытно, другие варианты использования для этого типа механизма синхронизации.

2 ответа

Решение

Простой пример, который я часто вижу в своей работе, - счетчик статистики. Если вы хотите посчитать, сколько раз происходит событие, но не нуждаетесь в какой-либо синхронизации между потоками, кроме обеспечения безопасности приращения, используяmemory_order_relaxed имеет смысл.

static std::atomic<size_t> g_event_count_;

void HandleEvent() {
  // Increment the global count. This operation is safe and correct even
  // if there are other threads concurrently running HandleEvent or
  // PrintStats.
  g_event_count_.fetch_add(1, std::memory_order_relaxed);

  [...]
}

void PrintStats() {
  // Snapshot the "current" value of the counter. "Current" is in scare
  // quotes because the value may change while this function is running.
  // But unlike a plain old size_t, reading from std::atomic<size_t> is
  // safe.
  const size_t event_count =
      g_event_count_.load(std::memory_order_relaxed);

  // Use event_count in a report.
  [...]
}

В обоих случаях нет необходимости использовать более сильный порядок памяти. На некоторых платформах это может оказать негативное влияние на производительность.

Считыватель событий в этом случае может быть подключен к сокету X11, где частота событий зависит от действий пользователя (изменение размера окна, ввод текста и т. Д.), А также если диспетчер событий потока в графическом интерфейсе проверяет события через регулярные промежутки времени (например, из-за некоторого таймера). события в пользовательском приложении) мы не хотим без необходимости блокировать поток чтения событий, получая блокировку в общей очереди событий, которая, как мы знаем, пуста. Мы можем просто проверить, было ли что-либо поставлено в очередь, используя атомарный метод dataReady. Это также известно как шаблон "Двойная проверка блокировки".

namespace {
std::mutex mutex;
std::atomic_bool dataReady(false);
std::atomic_bool done(false);
std::deque<int> events; // shared event queue, protected by mutex
}

void eventReaderThread()
{
    static int eventId = 0;
    std::chrono::milliseconds ms(100);
    while (true) {
        std::this_thread::sleep_for(ms);
        mutex.lock();
        eventId++; // populate event queue, e.g from pending messgaes on a socket
        events.push_back(eventId);
        dataReady.store(true, std::memory_order_release);
        mutex.unlock();
        if (eventId == 10) {
            done.store(true, std::memory_order_release);
            break;
        }
    }
}

void guiThread()
{
    while (!done.load(std::memory_order_acquire)) {
        if (dataReady.load(std::memory_order_acquire)) { // Double-checked locking pattern
            mutex.lock();
            std::cout << events.front() << std::endl;
            events.pop_front();
            // If consumer() is called again, and producer() has not added new events yet,
            // we will see the value set via this memory_order_relaxed.
            // If producer() has added new events, we will see that as well due to normal
            // acquire->release.
            // relaxed docs say: "guarantee atomicity and modification order consistency"
            dataReady.store(false, std::memory_order_relaxed);
            mutex.unlock();
        }
    }
}

int main()
{
    std::thread producerThread(eventReaderThread);
    std::thread consumerThread(guiThread);
    producerThread.join();
    consumerThread.join();
}
Другие вопросы по тегам