Boost, указатель на мьютекс, это будет работать?boost :: mutex и std :: vector, некопируемая проблема - PullRequest
2 голосов
/ 17 февраля 2012

Следующий код выдаст мне ошибку, так как boost :: mutex не копируется, а xyz.push_back () является конструктором копирования.

class XYZ
{
    public:
        double x;
        boost::mutex x_mutex;
}

vector<XYZ> xyz;
xyz.push_back(XYZ());

Итак, я попробовал что-то вроде этого:

class XYZ
{
    public:
        double x;
        boost::mutex * x_mutex;
}

vector<XYZ> xyz;
xyz.push_back(XYZ());

Это соответствует без ошибок, но вопрос в том, "будет ли этот мьютекс работать так, как должен?"Это хороший способ инкапсулировать мьютекс внутри класса, а затем создать вектор этого класса?

Спасибо.

1 Ответ

6 голосов
/ 17 февраля 2012

Здесь есть два вопроса:

  1. Будет ли мьютекс создан правильно?

  2. Будет ли мьютекс использоваться правильно?

Поскольку вопрос стоит, ответ на 1. НЕТ. Указатель мьютекса не указывает на мьютекс.

Так что вам нужно сделать, чтобы добавить соответствующий конструктор. А поскольку вам нужен конструктор, вам, вероятно, потребуется реализовать деструктор, конструктор копирования и оператор присваивания, если вы хотите, чтобы ваш класс вел себя правильно.

Либо

XYZ::XYZ() : x(0), x_mutex(new boost::mutex) {}
XYZ::~XYZ() { delete x_mutex; }
XYZ::XYZ( const XYZ & xyz ) : x(xyz.x), x_mutex( new boost::mutex ) {}
XYZ& XYZ::operator=( const XYZ & xyz ) { x=xyz.x; }

или

explicit XYZ::XYZ( boost::mutex * m ) : x(0), x_mutex(m) {}
// Strictly speaking we dont need these as the default version does the right thing.
XYZ::~XYZ() {}
XYZ::XYZ( const XYZ & xyz ) : x(xyz.x), x_mutex( xyz.x_mutex ) {}
XYZ& XYZ::operator=( const XYZ & xyz ) { x=xyz.x; x_mutex = xyz.x_mutex; }

Было бы то, что я ожидал.

В первом случае каждый экземпляр и копия объекта имеет свой собственный мьютекс. Во втором каждый объект делит мьютекс со своими копиями, но мьютекс должен быть создан до экземпляров.

Существует третий вариант, в котором мьютекс может быть создан конструктором и доступен всем экземплярам, ​​но для этого вы держите shared_ptr вместо мьютекса вместо необработанного указателя.

class XYZ
{
  public:
    double x;
    boost::shared_ptr<boost::mutex> x_mutex;
    XYZ() : x(0), x_mutex( new boost::mutex ) {}
    // Default versions of the other three do the right thing.
};

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

Теперь для хитрой части «Будет ли мьютекс использоваться правильно?». Чтобы ответить на этот вопрос, нам нужно знать, как объекты распределяются между потоками, каковы общие данные, которые мьютекс должен защищать.

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

//Main thread
std::vector<XYZ> v;
for(unsigned int i=0; i<10; ++i)
  v.push_back(XYZ());

//Several worker threads like this
j = rand()%10;
v[j].x_mutex->lock();
v[j].x+=1;
v[j].x_mutex->unlock();

Если x действительно является типом указателя и то, на что он указывает, распределяется между потоками, то ваш код, вероятно, выглядит следующим образом, и правильная версия используемого кода - 2 или 3.

//Main thread
std::vector<XYZ> v;
X * xa;
boost::mutex xa_mutex;
X * xb;
boost::mutex xb_mutex;

for(unsigned int i=0; i<5; ++i)
  v.push_back(XYZ(xa,xa_mutex));

for(unsigned int i=0; i<5; ++i)
  v.push_back(XYZ(xb,xb_mutex));

//Several worker threads like this
j = rand()%10;
v[j].x_mutex->lock(); 
v[j].x->do_something();
v[j].x_mutex->unlock();

Ключевым моментом является то, что существует один мьютекс на общий ресурс.

Обратите внимание, что технически вектор v является общим ресурсом в обоих случаях и также должен быть защищен мьютексом, если он будет изменен после создания. Однако такая блокировка (правильно) уничтожит весь параллелизм ... поэтому давайте пока проигнорируем это;)

...