Pregunta

Tengo una clase personalizada que utiliza impulsar mutex y cerraduras como esto (sólo partes pertinentes):

template<class T> class FFTBuf
{
    public:
        FFTBuf(); 
        [...]
        void lock();
        void unlock();
    private:
        T *_dst;
        int _siglen;
        int _processed_sums;
        int _expected_sums;
        int _assigned_sources;
        bool _written;
        boost::recursive_mutex _mut;
        boost::unique_lock<boost::recursive_mutex> _lock;
};

template<class T> FFTBuf<T>::FFTBuf() : _dst(NULL), _siglen(0),
    _expected_sums(1), _processed_sums(0), _assigned_sources(0),
    _written(false), _lock(_mut, boost::defer_lock_t())
{
}

template<class T> void FFTBuf<T>::lock()
{
    std::cerr << "Locking" << std::endl;
    _lock.lock();
    std::cerr << "Locked" << std::endl;
}

template<class T> void FFTBuf<T>::unlock()
{
    std::cerr << "Unlocking" << std::endl;
    _lock.unlock();
}

Si intento para bloquear más de una vez el objeto desde el mismo hilo , consigo una excepción (lock_error):

#include "fft_buf.hpp"

int main( void ) {
    FFTBuf<int> b( 256 );
    b.lock();
    b.lock();
    b.unlock();
    b.unlock();

    return 0;
}

Esta es la salida:

sb@dex $ ./src/test
Locking
Locked
Locking
terminate called after throwing an instance of 'boost::lock_error'
   what(): boost::lock_error
zsh: abort    ./src/test

¿Por qué sucede esto? Estoy entendiendo incorrectamente algún concepto?

¿Fue útil?

Solución

Prueba esto:

template<class T> void FFTBuf<T>::lock()
{
    std::cerr << "Locking" << std::endl;
     _mut.lock();
    std::cerr << "Locked" << std::endl;
}

template<class T> void FFTBuf<T>::unlock()
{
    std::cerr << "Unlocking" << std::endl;
    _mut.unlock();
}

Se utiliza la misma instancia de _lock unique_lock dos veces y este es un problema. Usted tiene que utilizar directamente métodos bloquear () y unock () de la mutex recursivo o utilizar dos instancias diferentes de unique_lock como ejemplo enemigo _lock y _lock_2;.

Actualizar

Me gustaría añadir que su clase tiene métodos lock() pública y unlock() y desde mi punto de vista en un programa real, es una mala idea. También tener unique_lock como miembro de la clase en un programa real debe ser a menudo una mala idea.

Otros consejos

Como su nombre lo indica, el objeto mutex es recursive pero el bloqueo no lo es.

Dicho esto, usted tiene aquí un problema de diseño. Las operaciones de bloqueo sería mejor no ser accesible desde el exterior.

class SynchronizedInt
{
public:
  explicit SynchronizedInt(int i = 0): mData(i) {}

  int get() const
  {
    lock_type lock(mMutex);
    toolbox::ignore_unused_variable_warning(lock);

    return mData;
  }

  void set(int i)
  {
    lock_type lock(mMutex);
    toolbox::ignore_unused_variable_warning(lock);

    mData = i;
  }


private:
  typedef boost::recursive_mutex mutex_type;
  typedef boost::unique_lock<mutex_type> lock_type;

  int mData;
  mutable mutex_type mMutex;
};

El punto principal de la recursive_mutex es para permitir el bloqueo de la cadena en un hilo dado que puede producirse si tiene operaciones complejas que requieren cada otros en algún caso.

Por ejemplo, vamos a añadir pellizco obtener:

int SynchronizedInt::UnitializedValue = -1;

int SynchronizedInt::get() const
{
  lock_type lock(mMutex);
  if (mData == UnitializedValue) this->fetchFromCache();
  return mData;
}

void SynchronizedInt::fetchFromCache()
{
  this->set(this->fetchFromCacheImpl());
}

¿Dónde está el problema?

  • get adquiere la cerradura de mMutex
  • que llama que llama a fetchFromCache set
  • set intentos para adquirir la cerradura ...

Si no tuviéramos un recursive_mutex, esto sería un fracaso.

El bloqueo no debe ser parte de la ressource protegida, sino de la persona que llama y cuando tenga una persona que llama por el hilo. Ellos deben utilizar diferentes unique_lock.

El propósito de unique_lock es para bloquear y liberar el mutex con RAII, por lo que no tiene que llamar de desbloqueo de forma explícita.

Cuando se declara la unique_lock el interior de un cuerpo de método, pertenecerá a la pila de subproceso de llamada.

Así que un uso más correcto es:

#include <boost/thread/recursive_mutex.hpp>
#include <iostream>

template<class T>
class FFTBuf
{
public :
    FFTBuf()
    {
    }

    // this can be called by any thread
    void exemple() const
    {
        boost::recursive_mutex::scoped_lock lock( mut );
        std::cerr << "Locked" << std::endl;

        // we are safe here
        std::cout << "exemple" << std::endl ;

        std::cerr << "Unlocking ( by RAII)" << std::endl;
    }

    // this is mutable to allow lock of const FFTBuf
    mutable boost::recursive_mutex mut;
};    

int main( void )
{
    FFTBuf< int > b ;

    {
        boost::recursive_mutex::scoped_lock lock1( b.mut );
        std::cerr << "Locking 1" << std::endl;

        // here the mutex is locked 1 times

        {
            boost::recursive_mutex::scoped_lock lock2( b.mut );
            std::cerr << "Locking 2" << std::endl;

            // here the mutex is locked 2 times

            std::cerr << "Auto UnLocking 2 ( by RAII) " << std::endl;
        }

        b.exemple();

        // here the mutex is locked 1 times

        std::cerr << "Auto UnLocking 1 ( by RAII) " << std::endl;
    }

    return 0;
}

Tenga en cuenta lo mutable en el mutex para los métodos const.

Y los tipos impulso de exclusión mutua tienen un scoped_lock typedef que es el tipo bueno unique_lock.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top