Pergunta

Eu tenho uma classe que é compartilhada entre vários projetos, alguns usos são de tiro único e alguns são multi-thread. Os usuários de tiro único não querem a sobrecarga do bloqueio mutex, e os usuários com vários thread não querem fazer seu próprio bloqueio e querem ser capazes de executar opcionalmente no "modo de thread único". Então, eu gostaria de ser capaz de selecionar entre mutexes reais e "dummy" em tempo de execução.

Idealmente, eu teria um shared_ptr<something> e atribua um objeto mutex real ou falso. Eu "travaria" isso sem levar em consideração o que há nele.

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

Agora existe um signals2::dummy_mutex Mas não compartilha uma classe base comum com boost::mutex.

Então, o que é uma maneira elegante de selecionar entre um mutex real e um mutex dummy (aquele em sinais2 ou algo mais) sem tornar o código de bloqueio/guarda mais complicado do que o exemplo acima?

E, antes de apontar as alternativas:

  • Eu poderia selecionar uma implementação no momento da compilação, mas as macros do pré -processador são feias e a manutenção das configurações de projeto é doloroso para nós.
  • Os usuários da classe em um ambiente multithread não desejam assumir a responsabilidade de bloquear o uso da classe, em vez de fazer com que a classe faça seu próprio travamento internamente.
  • Existem muitas APIs e usos existentes envolvidos para um "invólucro seguro para threads" ser uma solução prática.
Foi útil?

Solução

Que tal algo como isso? É não testado, mas deve estar perto de OK. Você pode considerar fazer com que a classe de modelo mantenha um valor em vez de um ponteiro se seus mutexes suportarem os tipos certos de construções. Caso contrário, você poderá especializar a classe MyMutex para obter comportamento de valor.

Também não está tomando cuidado com a cópia ou a destruição. Deixo isso como um exercício para o leitor;) (Shared_ptr ou armazenar um valor em vez de um ponteiro deve consertar isso)

Ah, e o código seria melhor usando RAII em vez de bloqueio/desbloqueio explícito ... mas essa é uma pergunta diferente. Eu assumo que é isso que o único_lock do seu código faz?

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 );
     ...
   }

}

Outras dicas

Desde as duas classes mutex signals2::dummy_mutex e boost::mutex Não compartilhe uma classe base comum, você pode usar algo como "Polimorfismo externo"Para permitir que eles sejam tratados polimorficamente. Você os usaria como travando estratégias Para uma interface mutex/bloqueio comum. Isso permite que você evite usar "if"Declarações na implementação do bloqueio.

NOTA: Isso é basicamente o que a solução proposta de Michael implementa. Eu sugiro ir com a resposta dele.

Você já ouviu falar sobre Policy-based Design ?

Você pode definir um Lock Policy interface e o usuário pode escolher qual política deseja. Para facilitar o uso, a política "padrão" é precisas usando uma variável de tempo de compilação.

#ifndef PROJECT_DEFAULT_LOCK_POLICY
#define PROJECT_DEFAULT_LOCK_POLICY TrueLock
#endif

template <class LP = PROJECT_DEFAULT_LOCK_POLICY>
class MyClass {};

Dessa forma, seus usuários podem escolher suas políticas com um switch simples de tempo de compilação e podem substituí-lo uma instância por vez;)

Isso não é suficiente?

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

Em geral, um mutex só é necessário se o recurso for compartilhado entre vários processos. Se uma instância do objeto for única para um processo (possivelmente multithread), uma seção crítica geralmente será mais apropriada.

No Windows, a implementação de um único thread de uma seção crítica é um fictício. Não tenho certeza de qual plataforma você está usando.

Apenas para sua informação, aqui está a implementação com quem acabei.

Eu acabei com a classe base abstrata, mesclando-a com a implementação de "fictício". Observe também o shared_ptr-classe derivada com um operador de conversão implícito. Um pouco complicado demais, eu acho, mas isso me permite usar shared_ptr<IMutex> objetos onde eu usei anteriormente boost::mutex objetos com zero alterações.

arquivo de cabeçalho:

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;
};

Arquivo de implementação:

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);
}

Esta é a minha solução:

std::unique_lock<std::mutex> lock = dummy ?
     std::unique_lock<std::mutex>(mutex, std::defer_lock) :
     std::unique_lock<std::mutex>(mutex);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top