Реализуйте высокопроизводительный мьютекс, похожий на мьютекс Qt
У меня есть многопоточное научное приложение, в котором несколько вычислительных потоков (по одному на ядро) должны хранить свои результаты в общем буфере. Это требует мьютексного механизма.
Рабочие потоки тратят только небольшую часть своего времени на запись в буфер, поэтому мьютекс разблокируется большую часть времени, и блокировки имеют высокую вероятность немедленного успеха, не дожидаясь разблокировки другого потока.
В настоящее время я использовал Qt QMutex для этой задачи, и он работает хорошо: мьютекс имеет незначительные накладные расходы.
Однако я должен перенести его только на c ++ 11 / STL. При использовании std :: mutex производительность падает на 66%, и потоки проводят большую часть своего времени, блокируя мьютекс.
После другого вопроса я понял, что Qt использует механизм быстрой блокировки, основанный на простом атомарном флаге, оптимизированный для случаев, когда мьютекс еще не заблокирован. И возвращается к системному мьютексу, когда происходит одновременная блокировка.
Я хотел бы реализовать это в STL. Есть ли простой способ, основанный на std :: atomic и std :: mutex? Я копался в коде Qt, но он кажется слишком сложным для моего использования (мне не нужны тайм-ауты блокировок, pimpl, небольшой размер и т. Д.).
Изменить: я пробовал спинлок, но это не работает, потому что:
Периодически (каждые несколько секунд) другой поток блокирует мьютексы и очищает буфер. Это занимает некоторое время, поэтому все рабочие потоки блокируются в это время. Спин-блокировки делают расписание занятым, в результате чего сброс будет в 10-100 раз медленнее, чем с надлежащим мьютексом. Это не приемлемо
Изменить: я пробовал это, но это не работает (блокирует все темы)
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, вместо ожидания он заполняет очередь (которая не используется другими потоками) и продолжаетКогда поток получает блокировку, он обновляет буфер с текущим результатом, но также с результатами, сохраненными в очереди (он обрабатывает свою очередь)Промывка буфера была сделана намного эффективнее: блокирующая часть меняет только два указателя.