многопоточность — безопасна ли двойная проверка блокировки в C ++ для однонаправленной передачи данных?

Я унаследовал приложение, которое я пытаюсь улучшить производительность, и в настоящее время он использует мьютексы (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);
}

Теперь мне кажется, что использование этого метода в быстром потоке может немного улучшить производительность:

  • Есть только одно место, где атомный флаг используется вне контроля мьютекса, и тот факт, что это атомный флаг, означает, что его состояние должно быть согласованным там.
  • Даже если это не последовательно, вторая проверка внутри области блокировки мьютекса должна обеспечить предохранительный клапан (он перепроверяется, когда мы знать это соответствует).
  • Передача данных только Когда-либо выполняется под контролем мьютекса, так что всегда должен быть последовательным.
  • Внешний цикл в быстром потоке означает, что ненужные блокировки мьютекса будут исключены — они будут выполняться только в том случае, если флаг имеет значение true (или «half-true», возможно, несовместимое состояние).
  • Внутренний if позаботится о той «полуправдной» возможности, что между проверкой и блокировкой мьютекса флаг будет очищен.

Я не могу увидеть любые дыры в этой стратегии, но, учитывая, что я только вхожу в атомарность / многопоточность в мире стандартного C ++, возможно, я чего-то упускаю.

Есть ли явные проблемы в использовании этого метода?

1

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]