Каковы некоторые варианты использования для 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();
}