Вопрос

У меня есть класс, который разделяет между несколькими проектами, некоторые виды использования его являются однопоточными, а некоторые являются многопоточными. Пользователи одноретики не хотят, чтобы накладные расходы блокировки 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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top