У меня есть параллельный объект, который может или может содержать указатель на функцию в каждый момент времени. Схема объекта выглядит так:
struct ConcurrentObject{
//variables
std::atomic<void(*)()> callback;
}
поэтому один поток может решить, что он хочет присоединить обратный вызов с этим объектом и передать его вперед:
ConcurrentObject* co = new ConcurrentObject(); //I'm using smart pointers, no worries.
//do some logic
co->callback = someCallback; //void(*)() , this may be difference callback every time
Я получаю этот объект после его модификации и проверяю, доступен ли обратный вызов:
auto co = aquireConcurrentObject();
auto callback = co->callback.load();
if (callback){
callback()
}
теперь мы знаем, что без указания какого-либо порядка памяти, по умолчанию передается упорядоченная память memory_order_seq_cst
что говорит компилятору (в двух словах): «не разбирайте никакие инструкции чтения или записи, чтобы сделать программу быстрее, сохраняйте относительный порядок инструкций, заданный кодом, и делайте его видимым через процессор»
мы также знаем, что это очень важно для производительности, так как компилятор гораздо более ограничен в действиях, которые он может предпринять.
Мой вопрос — делает std::memory_order_relaxed
достаточно для этого действия?
Да, вы правы, в вашем примере std :: memory_order_relaxed безопасно использовать, потому что ваш код полагается только на тот факт, что обратный вызов является атомарным. Ваш код не подвержен возможному переупорядочению операций с памятью
Порядок в памяти для обращений указателя обратного вызова влияет на «видимость» переменных, используемых обратным вызовом.
Если ваш обратный звонок:
1) есть constexpr-например, он не использует ничего, кроме своих аргументов и постоянных глобальных переменных, или
2) использует только переменные, которые инициализируется до (случается, перед тем) возможное использование обратного вызова,
затем с помощью std::memory_order_relaxed
нормально и для магазина и для загрузки.
Но если ваш код под //do some logic
инициализирует некоторые переменные, используемые обратным вызовом, затем вы должны использовать по крайней мере std::memory_order_release
/std::memory_order_acquire
для хранения и загрузки соответственно. В противном случае при выполнении обратного вызова эти переменные могут быть неинициализированы (более строго, это будет гонка данных с точки зрения стандарта C ++ 11, который является неопределенным поведением).
У вас есть способ измерить влияние на производительность? Изменение порядка памяти может выглядеть как хорошая загрузка при загрузке (кажется, что это действительно так), но на самом деле, в большинстве приложений — это не имеет значения.
Изменение модели памяти здесь означает, что любой, кто будет поддерживать этот код, должен быть очень осторожным, чтобы не сломать его, так что это еще один риск, который вы берете на себя, когда делаете такие вещи.
Такие оптимизации должен быть очень хорошо задокументирован и выбран после того, как доказано, что он является узким местом в производительности. Не связывайтесь с этим, если вам не нужно.