#c #multithreading #atomic
#c #многопоточность #атомарный
Вопрос:
В настоящее время я работаю над проектом, который имеет довольно жесткие циклы и требования к реальному времени. Преждевременная оптимизация здесь определенно не порок. Я хочу максимально использовать возможные relaxed
варианты C atomics для достижения своих целей. Вот моя (небезопасная для потоков) настройка:
// Shared data structure
uint8 data[100];
uint64 timeStamp = 0;
// Consumer threads 1...N run this
void loopConsume() {
uint64 lastTimeStamp = 0;
uint8 localData[100];
for (;;) {
while (timeStamp == lastTimeStamp);
memcpy(localData, latestData.data, 100);
lastTimeStamp = timeStamp;
doSomething(localData);
}
}
// Producer callback (singleton)
void produce(uint8 *newData) {
memcpy(latestData.data, newData, 100);
latestData.timeStamp ;
}
Вот неуклюжая (но, по-видимому, правильная) попытка сделать ее потокобезопасной (и потокорректной) с использованием 2 атомарных последовательной согласованности по умолчанию:
#include <atomic>
// Shared variables
uint8 data[100];
std::atomic<uint64> timeStamp = 0;
std::atomic<int32> numReaders = 0; // -1 means producer is writing
// Consumer threads 1...N run this
void loopConsume() {
uint64 lastTimeStamp = 0;
uint8 localData[100];
for (;;) {
uint64 t = timeStamp.load();
while (t == lastTimeStamp) t = timeStamp.load();
lastTimeStamp = t;
for (;;) {
int32 r = numReaders.load();
if (r >= 0 amp;amp; numReaders.compare_exchange_weak(r, r 1))
break;
}
memcpy(localData, data, 100);
doSomething(localData);
numReaders.fetch_sub(1);
}
}
// Producer callback (singleton)
void produce(uint8 *newData) {
while (!numReaders.compare_exchange_weak(0, -1));
memcpy(data, newData, 100);
timeStamp.fetch_add(1);
numReaders.store(0);
}
Как вы можете видеть, существует множество атомарных операций, все из которых по умолчанию используют последовательную согласованность. Я уверен, что многие из них могут быть смягчены. Но какие из них? Могу ли я обойтись меньшим количеством операций или более простой настройкой?
Другой вопрос — это разногласия. Я ожидаю, что читателей будет немного, и они быстро скопируют свои данные в свою локальную переменную. Есть ли тогда какое-либо реальное преимущество в том, чтобы иметь вид блокировки чтения-записи (как указано выше) или просто обычный мьютекс? Если в этом случае обычный мьютекс работает быстрее, могу ли я каким-то образом включить его в атомарную переменную метки времени?
Большое спасибо!
Комментарии:
1. Я думаю, вас может заинтересовать этот доклад с cppcon 2016: m.youtube.com/watch?v=IB57wIf9W1k — Дж.Ф. Бастьен: Ни один здравомыслящий компилятор не стал бы оптимизировать атомику.
2. Вы не говорите нам, на каком оборудовании вы будете запускать это. В системе x86 слабые и сильные версии, скорее всего, будут идентичны, а синхронизация аппаратного кэша обеспечивает последовательную согласованность, независимо от того, запрашиваете вы это или нет (за исключением, возможно , memory_order_relaxed ).
3. Такой код будет ужасно работать на многих процессорах x86. Вы ошибаетесь в том, что преждевременная оптимизация здесь не является пороком, именно здесь вы можете нанести наибольший ущерб, оптимизируя, не понимая, что вы заставляете делать процессор. Вы серьезно пытаетесь использовать функцию сравнения и обмена для ОЖИДАНИЯ ? Вы с ума сошли? (Знаете ли вы, что они выполняют безусловную запись на большинстве процессоров x86? Вы знаете, что они делают с межядерными шинами? Знаете ли вы, как они взаимодействуют с hyper-threading? Если нет, ТО ПОЧЕМУ ВЫ ИХ ИСПОЛЬЗУЕТЕ? Если да, ТО ПОЧЕМУ ВЫ ИХ ИСПОЛЬЗУЕТЕ!)