Выберите Mutex или Bucky Mutex во время выполнения
-
24-09-2019 - |
Вопрос
У меня есть класс, который разделяет между несколькими проектами, некоторые виды использования его являются однопоточными, а некоторые являются многопоточными. Пользователи одноретики не хотят, чтобы накладные расходы блокировки Mutex, и многопоточные пользователи не хотят выполнять свои собственные блокировки и хотите иметь возможность обязательно запускать в «режиме однопоточной». Поэтому я хотел бы выбрать между реальными и «манекенными» мьютечками во время выполнения.
В идеале я бы имел shared_ptr<something>
и назначить либо реальный, либо поддельный объект METEX. Я бы тогда «заблокировал» это без учета того, что в нем.
unique_lock<something> guard(*mutex);
... critical section ...
Теперь есть signals2::dummy_mutex
Но он не разделяет общий базовый класс с boost::mutex
.
Итак, какой элегантный способ выбрать между настоящим мьютеком и манекенным MUTEX (либо тот, кто в сигналах2 или что-то еще), не делая код замка / охрана более сложным, чем пример выше?
И, прежде чем указывать на альтернативы:
- Я мог бы выбрать реализацию во время компиляции, но макросы препроцессора являются уродливыми и поддерживать конфигурации проекта больно для нас.
- Пользователи класса в многопоточной среде не хотят брать на себя ответственность за блокировку использования класса, а не в том, что класс делает его собственную блокировку внутри.
- Существует слишком много API и существующие употребления, участвующие для «бесперебойной обертки», чтобы быть практическим решением.
Решение
Как насчет чего-то вроде этого? Его непроверенные, но должно быть близко к ОК. Вы можете подумать о том, чтобы сделать класс Class удерживайте значение, а не указатель, если ваши Mutexes поддерживают правильные виды конструкций. В противном случае вы можете специализировать класс MyMutex, чтобы получить ценное поведение.
Также не осторожно о копировании или уничтожении.
О, и код будет приятнее, используя Raii, а не явный замок / разблокировать ... Но это другой вопрос. Я предполагаю, что делает то, что делает Unique_lock в вашем коде?
struct IMutex
{
virtual ~IMutex(){}
virtual void lock()=0;
virtual bool try_lock()=0;
virtual void unlock()=0;
};
template<typename T>
class MyMutex : public IMutex
{
public:
MyMutex(T t) : t_(t) {}
void lock() { t_->lock(); }
bool try_lock() { return t_->try_lock(); }
void unlock() { t_->unlock(); }
protected:
T* t_;
};
IMutex * createMutex()
{
if( isMultithreaded() )
{
return new MyMutex<boost::mutex>( new boost::mutex );
}
else
{
return new MyMutex<signal2::dummy_mutex>( new signal2::dummy_mutex );
}
}
int main()
{
IMutex * mutex = createMutex();
...
{
unique_lock<IMutex> guard( *mutex );
...
}
}
Другие советы
Поскольку два класса Mutex signals2::dummy_mutex
а также boost::mutex
Не делитесь общим базовым классом, вы можете использовать что-то вроде «внешний полиморфизм«Разрешить им лечиться полиморфно. Вы бы тогда используете их как блокировка стратегии Для общего интерфейса Mutex / Lock. Это позволяет избежать использования "if
«Заявления в реализации блокировки.
ПРИМЕЧАНИЕ: Это в основном то, что предложил Майклское решение для решения. Я бы предложил идти со своим ответом.
Вы когда-нибудь слышали о Policy-based Design
?
Вы можете определить Lock Policy
Интерфейс, и пользователь может выбрать, какую политику она желает. Для простоты использования политика «по умолчанию» определяется с использованием переменной времени компиляции.
#ifndef PROJECT_DEFAULT_LOCK_POLICY
#define PROJECT_DEFAULT_LOCK_POLICY TrueLock
#endif
template <class LP = PROJECT_DEFAULT_LOCK_POLICY>
class MyClass {};
Таким образом, ваши пользователи могут выбрать свою политику с простой коммутационным коммутатором и могут переопределить его один экземпляр одновременно;)
Это недостаточно?
class SomeClass
{
public:
SomeClass(void);
~SomeClass(void);
void Work(bool isMultiThreaded = false)
{
if(isMultiThreaded)
{
lock // mutex lock ...
{
DoSomething
}
}
else
{
DoSomething();
}
}
};
В целом, Mutex необходим только в том случае, если ресурс распределяется между несколькими процессами. Если экземпляр объекта уникален для (возможно, многопоточной) процесса, то критический раздел часто более подходит.
В Windows, однопоточная реализация критического раздела является манекен один. Не уверен, какую платформу вы используете.
Просто FYI, вот реализация, с которой я оказался.
Я сделал с абстрактным базовым классом, слияя его с реализацией NO-OP «Dummy». Также обратите внимание shared_ptr
-Дедированный класс с неявным оператором преобразования. Думаю, слишком сложно, но это позволяет мне использовать shared_ptr<IMutex>
объекты, где я ранее использовал boost::mutex
объекты с нулевыми изменениями.
Файл заголовка:
class Foo {
...
private:
struct IMutex {
virtual ~IMutex() { }
virtual void lock() { }
virtual bool try_lock() { return true; }
virtual void unlock() { }
};
template <typename T> struct MutexProxy;
struct MutexPtr : public boost::shared_ptr<IMutex> {
operator IMutex&() { return **this; }
};
typedef boost::unique_lock<IMutex> MutexGuard;
mutable MutexPtr mutex;
};
Файл реализации:
template <typename T>
struct Foo::MutexProxy : public IMutex {
virtual void lock() { mutex.lock(); }
virtual bool try_lock() { return mutex.try_lock(); }
virtual void unlock() { mutex.unlock(); }
private:
T mutex;
};
Foo::Foo(...) {
mutex.reset(single_thread ? new IMutex : new MutexProxy<boost::mutex>);
}
Foo::Method() {
MutexGuard guard(mutex);
}
Это мое решение:
std::unique_lock<std::mutex> lock = dummy ?
std::unique_lock<std::mutex>(mutex, std::defer_lock) :
std::unique_lock<std::mutex>(mutex);