У меня многопоточное научное приложение, в котором несколько вычислительных потоков (по одному на ядро) должны хранить свои результаты в общем буфере. Для этого требуется механизм мьютекса.
Рабочие потоки тратят лишь небольшую часть времени на запись в буфер, поэтому мьютексы разблокируются большую часть времени, и блокировки имеют высокую вероятность успеха сразу, не дожидаясь разблокировки другого потока.
В настоящее время я использовал Qt QMutex для задачи, и он работает хорошо: мьютекс имеет незначительные накладные расходы.
Однако мне нужно перенести его только на С++ 11/STL. При использовании std:: mutex производительность снижается на 66%, а потоки большую часть времени тратят на блокировку мьютекса.
После другого вопроса я понял, что Qt использует быстрый механизм блокировки на основе простого атомного флага, оптимизированного для случаев, когда мьютекс еще не заблокирован. И возвращается к системному мьютексу, когда происходит параллельная блокировка.
Я хотел бы реализовать это в STL. Есть простой способ, основанный на std:: atomic и std:: mutex? Я копал в Qt-коде, но мне кажется, что это слишком сложно для моего использования (мне не нужны тайм-ауты блокировок, pimpl, малая занимаемая площадь и т.д.).
Изменить: я пробовал спин-блокировку, но это плохо работает, потому что:
Периодически (каждые несколько секунд) другой поток блокирует мьютексы и сбрасывает буфер. Это занимает некоторое время, поэтому все рабочие потоки блокируются в это время. Винтовые блоки делают планирование занятым, что приводит к тому, что флеш будет на 10-100x медленнее, чем при соответствующем мьютексе. Это неприемлемо
Изменить: я пробовал это, но он не работает (блокирует все потоки)
class Mutex
{
public:
Mutex() : lockCounter(0) { }
void lock()
{
if(lockCounter.fetch_add(1, std::memory_order_acquire)>0)
{
std::unique_lock<std::mutex> lock(internalMutex);
cv.wait(lock);
}
}
void unlock();
{
if(lockCounter.fetch_sub(1, std::memory_order_release)>1)
{
cv.notify_one();
}
}
private:
std::atomic<int> lockCounter;
std::mutex internalMutex;
std::condition_variable cv;
};
Спасибо!
Изменить: окончательное решение
Быстрое мьютекс MikeMB работал очень хорошо.В качестве окончательного решения я сделал:
- Используйте простую прямую блокировку с помощью try_lock
- Когда поток не пытается try_lock, вместо ожидания он заполняет очередь (которая не используется совместно с другими потоками) и продолжает
- Когда поток получает блокировку, он обновляет буфер с текущим результатом, но также и с результатами, хранящимися в очереди (обрабатывает свою очередь)
- Буферная промывка была сделана гораздо более эффективно: блокирующая часть заменяет только два указателя.