Выберите мьютекс или фиктивный мьютекс во время выполнения - PullRequest
2 голосов
/ 02 февраля 2010

У меня есть класс, который используется несколькими проектами, некоторые его применения являются однопоточными, а некоторые - многопоточными. Однопоточные пользователи не хотят накладных расходов на мьютекс-блокировку, а многопоточные пользователи не хотят делать свою собственную блокировку и хотят иметь возможность работать в «однопоточном режиме». Поэтому я хотел бы иметь возможность выбирать между реальными и "фиктивными" мьютексами во время выполнения.

В идеале я бы имел shared_ptr<something> и назначил бы реальный или фальшивый объект мьютекса. Я бы тогда "запер" это безотносительно к тому, что в нем.

unique_lock<something> guard(*mutex);
... critical section ...

Сейчас существует signals2::dummy_mutex, но он не имеет общего базового класса с boost::mutex.

Итак, каков элегантный способ выбора между реальным мьютексом и фиктивным мьютексом (либо в сигналах2, либо чем-то еще), не делая код блокировки / защиты более сложным, чем в примере выше?

И, прежде чем указать на альтернативы:

  • Я мог бы выбрать реализацию во время компиляции, но макросы препроцессора уродливы, и поддержание конфигураций проекта нам больно.
  • Пользователи класса в многопоточной среде не хотят брать на себя ответственность за блокировку использования класса, а не за то, чтобы класс делал свою собственную блокировку внутри.
  • Слишком много API-интерфейсов и существующих способов использования для "поточно-ориентированной оболочки", чтобы быть практическим решением.

Ответы [ 7 ]

4 голосов
/ 02 февраля 2010

Как насчет этого? Это не проверено, но должно быть близко к ОК. Возможно, вы захотите сделать так, чтобы класс шаблона содержал значение, а не указатель если ваши мьютексы поддерживают правильные виды конструкций. В противном случае вы могли бы специализировать класс MyMutex для получения значения поведения.

Кроме того, это не относится к копированию или уничтожению .. Я оставляю это как упражнение для читателя;) (shared_ptr или сохранение значения вместо указателя должно исправить это)

Да, и код будет лучше, если использовать 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 );
     ...
   }

}
3 голосов
/ 02 февраля 2010

Поскольку два класса мьютекса signals2::dummy_mutex и boost::mutex не имеют общего базового класса, вы можете использовать что-то вроде " внешний полиморфизм ", чтобы позволить им обрабатываться полиморфно. Затем вы будете использовать их как блокировку стратегий для общего интерфейса мьютекс / блокировка. Это позволяет избежать использования операторов "if" в реализации блокировки.

ПРИМЕЧАНИЕ : Это в основном то, что реализует предложенное Майклом решение. Я бы предложил пойти с его ответом.

1 голос
/ 02 февраля 2010

Вы когда-нибудь слышали о 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 {};

Таким образом, ваши пользователи могут выбирать свои политики с помощью простого переключателя времени компиляции и могут переопределять его по одному экземпляру за раз;)

0 голосов
/ 14 сентября 2017

Это мое решение:

std::unique_lock<std::mutex> lock = dummy ?
     std::unique_lock<std::mutex>(mutex, std::defer_lock) :
     std::unique_lock<std::mutex>(mutex);
0 голосов
/ 02 февраля 2010

Только к вашему сведению, вот реализация, с которой я закончил.

Я покончил с абстрактным базовым классом, объединив его с неактивной "фиктивной" реализацией. Также обратите внимание на класс 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);
}
0 голосов
/ 02 февраля 2010

Как правило, мьютекс необходим, только если ресурс распределяется между несколькими процессами. Если экземпляр объекта уникален для (возможно, многопоточного) процесса, то критический раздел часто более уместен.

В Windows однопоточная реализация критического раздела является фиктивной. Не уверен, какую платформу вы используете.

0 голосов
/ 02 февраля 2010

Разве этого недостаточно?

   class SomeClass
    {
    public:
        SomeClass(void);
        ~SomeClass(void);
        void Work(bool isMultiThreaded = false)
        {
            if(isMultiThreaded)
           {
               lock // mutex lock ...
               {
                    DoSomething
               }
           }
           else
           {
                DoSomething();
           }
       }   
    };
...