Domanda

Ho riscontrato un problema che mi sembra preoccupante. Sembra che ho trovato una situazione abbastanza facile da aggirare, ma che potrebbe causare problemi se a) ho un intervallo di concentrazione durante la programmazione o b) qualcun altro inizia a implementare le mie interfacce e non sa come gestire questa situazione.

Ecco la mia configurazione di base:

Ho una classe astratta che sto usando come interfaccia generica per diversi tipi di dati. Ho adottato il paradigma dell'interfaccia pubblica non virtuale (Sutter, 2001) insieme al blocco con ambito per fornire un po 'di sicurezza del thread. Una classe di interfaccia di esempio sarebbe simile a questa (ho tralasciato i dettagli sul blocco dell'ambito e l'implementazione di mutex, poiché non credo siano rilevanti):

class Foo
{
public:
    A( )
    {
        ScopedLock lock( mutex );
        aImp( );
    }
    B( )
    {
        ScopedLock lock( mutex );
        bImp( );
    }
protected:
    aImp( ) = 0;
    bImp( ) = 0;
}

Spetta quindi all'utente implementare aImp e bImp, che è il punto in cui si presenta il problema. Se aImp esegue un'operazione che utilizza bImp, è estremamente facile (e quasi logico, in un certo senso) farlo:

class Bar
{
protected:
    aImp( )
    {
        ...
        B( );
        ...
    }
    bImp( )
    {
        ...
    }
}

Deadlock. Naturalmente, la soluzione semplice a questo è quella di chiamare sempre le funzioni virtuali protette piuttosto che le loro varianti pubbliche (sostituire B () con bImp () nello snippet sopra). Ma sembra ancora facile impiccarmi se commetto un errore, o peggio ancora permettere ad altri di impiccarsi.

Qualcuno ha un modo per tentare di impedire a un implementatore della classe astratta di chiamare quelle funzioni pubbliche in fase di compilazione, o altrimenti aiutare a evitare la soluzione di deadlock?

Solo per i calci, alcuni mutex consentono operazioni che eviteranno problemi di deadlock. Ad esempio, se lo implemento usando le funzioni di Windows EnterCriticalSection e LeaveCriticalSection, non c'è problema. Ma preferirei evitare funzionalità specifiche della piattaforma. Attualmente sto usando boost :: mutex e boost :: shared_mutex nella mia implementazione di blocco con ambito, e per quanto ho visto non tenta di evitare deadlock (che penso quasi preferisca).

È stato utile?

Soluzione

L'uso dell'eredità privata risolverà potenzialmente il tuo problema:

class Foo
{
public:
  void A( )
    {
      ScopedLock lock( mutex );
      aImp( );
    }
  void B( )
    {
      ScopedLock lock( mutex );
      bImp( );
    }

protected:
  virtual void aImp( ) = 0;
  virtual void bImp( ) = 0;
};

class FooMiddle : private Foo
{
public:
  using Foo::aImp;
  using Foo::bImp;
};

class Bar : public FooMiddle
{
  virtual void aImpl ()
  {
    bImp ();
    B ();                   // Compile error - B is private
  }
};

Derivando da Foo in privato, e quindi usando FooMiddle assicura che Bar non abbia accesso ad A o B. Tuttavia, bar è ancora in grado di sovrascrivere aImp e bImp, e le dichiarazioni di utilizzo in FooMiddle significano che possono ancora essere chiamate dal bar.

In alternativa, un'opzione che aiuterà ma non risolverà il problema è usare il modello Pimpl. Finiresti con qualcosa come segue:

class FooImpl
{
public:
  virtual void aImp( ) = 0;
  virtual void bImp( ) = 0;
};

class Foo
{
public:
  void A( )
    {
      ScopedLock lock( mutex );
      m_impl->aImp( );
    }
  void B( )
    {
      ScopedLock lock( mutex );
      m_impl->bImp( );
    }

private:
  FooImpl * m_impl;
}

Il vantaggio è che nelle classi derivanti da FooImpl non hanno più un "Foo" oggetto e quindi non può facilmente chiamare " A " o " B " ;.

Altri suggerimenti

Il tuo mutex non deve essere un mutex ricorsivo. Se non è un mutex ricorsivo, un secondo tentativo di bloccare il mutex nello stesso thread comporterà il blocco di quel thread. Poiché quel thread ha bloccato il mutex, ma è bloccato su quel mutex, hai un deadlock.

Probabilmente vuoi guardare:

boost::recursive_mutex

http://www.boost.org/doc /libs/1_32_0/doc/html/recursive_mutex.html

Dovrebbe implementare il comportamento mutex ricorsivo multipiattaforma. Nota Win32 CRITICAL_SECTION (usato tramite Enter / LeaveCriticalSection) sono ricorsivi, il che creerebbe il comportamento che descrivi.

Mentre un blocco ricorsivo risolverebbe il tuo problema, ho sempre pensato che, sebbene talvolta necessario, in molte situazioni un blocco ricorsivo viene usato come una semplice via d'uscita, bloccando troppo.

Il tuo codice pubblicato è ovviamente semplificato a scopo dimostrativo, quindi non sono sicuro che si applichi.

Ad esempio, supponiamo che l'utilizzo della risorsa X non sia thread-safe. Hai qualcosa del genere.

A() {
   ScopedLock
   use(x)
   aImp()
   use(x)
}

aImp() {
   ScopedLock
   use(x)
}

Ovviamente, ciò comporterebbe un deadlock.

L'uso dei blocchi molto più stretto, tuttavia, eliminerebbe il problema. L'utilizzo di blocchi in un ambito il più piccolo possibile è sempre una buona idea, sia per motivi di prestazioni, sia per evitare deadlock.

A() {
   {
      ScopedLock
      use(x)
   }
   aImp()
   {
      ScopedLock
      use(x)
   }
}

Hai avuto l'idea.

Sono consapevole che questo non è sempre possibile (o porterebbe a un codice orribilmente insufficiente), senza conoscere ulteriori dettagli non so se si applica al tuo problema. Ma ho pensato che valesse la pena pubblicare comunque.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top