Класс foo
имеет метод bar
. Согласно некоторому протоколу синхронизации, метод bar
конкретного объекта foo
будет вызываться только не более чем одним потоком в любой момент времени.Эффективная не-принудительная, проверка, мьютексы
Я хотел бы добавить очень легкий verification_mutex
, чтобы проверить эту ошибку/отладочную синхронизацию. Он будет использоваться так же, как обычный мьютекс:
class foo {
public:
void bar() {
std::lock_guard<verification_mutex> lk{m};
...
}
private:
mutable verification_mutex m;
};
однако, само по себе не обязательно блокировать или разблокировать все. Скорее всего, это будет throw
, если обнаружен многопоточный одновременный доступ. Дело в том, чтобы сделать его временной охват как можно более низким (включая его влияние на другой код, например, на memory barriers).
Вот три варианта реализации verification_mutex
:
- обертка вокруг
std::mutex
, но сlock
реализованного проверкой, чтоtrylock
удалось (это просто, чтобы получить представление, очевидно, не очень быстро) - Атомная переменная, обозначающая текущий «блокирующий» поток id, с атомарными операциями
exchange
(см. Схему реализации ниже). - То же, что и 2, но без атомизма.
Эти правильные или неправильные (в частности, 2 и особенно 3)? Как они повлияют на производительность (например, окружающего кода)? Есть ли вообще превосходная альтернатива?
Редактировать Ответ на @SergeyA ниже это хорошо, но я, в частности, интересно, о барьерах памяти. Решение, не использующее их, было бы замечательным, так как это был бы ответ, дающий некоторое интуитивное объяснение, почему любое решение, исключающее их, обязательно потерпит неудачу.
Эскиз Реализация
#include <atomic>
#include <thread>
#include <functional>
class verification_mutex {
public:
verification_mutex() : m_holder{0}{}
void lock() {
if(m_holder.exchange(get_this_thread_id()) != 0)
throw std::logic_error("lock");
}
void unlock() {
if(m_holder.exchange(0) != get_this_thread_id())
throw std::logic_error("unlock");
}
bool try_lock() {
lock();
return true;
}
private:
static inline std::size_t get_this_thread_id() {
return std::hash<std::thread::id>()(std::this_thread::get_id());
}
private:
std::atomic_size_t m_holder;
};
Тройник должен фактически удерживать блокировку, иначе вы не можете определить, есть ли другой «там». Так что это будет не быстрее, чем блокировка. Насколько сильно вам нужно знать об ошибке? Надежно, или это просто тестирование? – Yakk
Почему вы пытаетесь сделать реенрантный замок? – SergeyA
Также обратите внимание, что добавление какой-либо синхронизации приведет к замедлению вашего действия из-за эффекта барьеров памяти (и синхронизация без барьеров памяти бесполезна) – SergeyA