Я унаследовал приложение, которое я пытаюсь улучшить производительность, и в настоящее время он использует мьютексы (std::lock_guard<std::mutex>
) передавать данные из одного потока в другой. Один поток является низкочастотным (медленным), который просто изменяет данные, которые будут использоваться другим.
Другой поток (который мы назовем быстрым) предъявляет довольно жесткие требования к производительности (он должен выполнять максимально возможное количество циклов в секунду), и мы считаем, что на это влияет использование мьютексов.
По сути, текущая логика:
slow thread: fast thread:
occasionally: very-often:
claim mutex claim mutex
change data use data
release mutex release mutex
Чтобы получить быстрый поток, работающий с максимальной пропускной способностью, я хотел бы поэкспериментировать с удалением количества блокировок мьютекса, которые он должен выполнить.
я подозреваемый здесь может быть полезен вариант шаблона проверки двойной блокировки. Я знаю, что у него есть серьезные проблемы с двунаправленным потоком данных (или созданием синглтона), но сферы ответственности в моем случае немного более ограничены с точки зрения того, какой поток выполняет какие операции (и когда) с общими данными.
По сути, медленный поток устанавливает данные и никогда не читает и не записывает их снова, пока не произойдет новое изменение. Быстрый поток использует и изменяет данные, но никогда не ожидает передачи какой-либо информации другому потоку. Другими словами, собственность в основном течет строго в одном направлении.
Я хотел посмотреть, сможет ли кто-нибудь выбрать дыры в стратегии, о которой я думаю.
Новая идея состоит в том, чтобы иметь два наборы данных, один текущий и один ожидающий. В моем случае нет необходимости в очереди, поскольку входящие данные перезаписывают предыдущие данные.
Ожидающие данные будут когда-либо записываться только медленным потоком под управлением мьютекса, и у него будет атомарный флаг, указывающий, что он записал и отказался от управления (на данный момент).
Быстрый поток будет продолжать использовать текущие данные (без мьютекса) до тех пор, пока не будет установлен атомарный флаг. Поскольку он отвечает за передачу данных, ожидающих обработки, он может обеспечить постоянство текущих данных.
В точке, где установлен флаг, он заблокирует мьютекс и, переведя его в ожидание текущего, сбросит флаг, разблокирует мьютекс и продолжит.
Так что, по сути, быстрый поток работает на полной скорости и блокирует мьютекс только тогда, когда знает ожидающие данные должны быть переданы.
Если вдаваться в более конкретные детали, класс будет иметь следующие члены данных:
std::atomic_bool m_newDataReady;
std::mutex m_protectData;
MyStruct m_pendingData;
MyStruct m_currentData;
Метод для получения новых данных в медленном потоке будет:
void NewData(const MyStruct &newData) {
std::lock_guard<std::mutex> guard(m_protectData);
m_newDataReady = false;
Transfer(m_newData, 'to', m_pendingData);
m_newDataReady = true;
}
Снятие флажка предотвращает даже попытку быстрого потока проверить новые данные, пока операция немедленной передачи не будет завершена.
Быстрый поток немного сложнее, используя флаг, чтобы свести блокировки мьютекса к минимуму:
while (true) {
if (m_newDataReady) {
std::lock_guard<std::mutex> guard(m_protectData);
if (m_newDataReady) {
Transfer(m_pendingData, 'to', m_currentData);
m_newDataReady = false;
}
}
Use (m_currentData);
}
Теперь мне кажется, что использование этого метода в быстром потоке может немного улучшить производительность:
if
позаботится о той «полуправдной» возможности, что между проверкой и блокировкой мьютекса флаг будет очищен.Я не могу увидеть любые дыры в этой стратегии, но, учитывая, что я только вхожу в атомарность / многопоточность в мире стандартного C ++, возможно, я чего-то упускаю.
Есть ли явные проблемы в использовании этого метода?
Задача ещё не решена.
Других решений пока нет …