boost :: scoped_lock не работает с локальной статической переменной?

#c #multithreading #locking #thread-safety #boost-thread

#c #многопоточность #блокировка #безопасность потоков #boost-поток

Вопрос:

Я создал следующий пример программы для работы с потоками boost:

 #pragma once
#include "boostthreadmutex.hpp"
#include <iostream>

class ThreadWorker
{
public:
    ThreadWorker() {}
    virtual ~ThreadWorker() {}

    static void FirstCount(int threadId)
    {
        boost::mutex::scoped_lock(mutex_);
        static int i = 0;

        for(i = 1; i <= 30; i  )
        {
            std::cout << i << ": Hi from thread:  " << threadId << std::endl;
        }

    }

private:
    boost::mutex mutex_;
};
  

основной класс:

 // ThreadTest.cpp
#include "stdafx.h"
#include "boostthreadthread.hpp"
#include "ThreadWorker.h"

int _tmain(int argc, _TCHAR* argv[])
{
    boost::thread thread1(amp;ThreadWorker::FirstCount, 1);
    boost::thread thread2(amp;ThreadWorker::FirstCount, 2);
    boost::thread thread3(amp;ThreadWorker::FirstCount, 3);

    thread1.join();
    thread2.join();
    thread3.join();

    std::string input;
    std::cout << "Press <enter> to finish...n";
    std::getline( std::cin, input );
    return 0;
}
  

Когда я запускаю это, я получаю следующий вывод:

 1: Hi from thread:  1
1: Hi from thread:  3
2: Hi from thread:  3
...
  

Похоже, что сначала туда попадает поток 1, а затем поток 3. Разве scoped_lock не должен препятствовать другим потокам входить в этот раздел кода? Разве первый поток, который запускает FirstCount(), не должен завершаться?

Обновить

Одна вещь, которую я считаю неправильной в моем коде, — это эта строка:

 boost::mutex::scoped_lock(mutex_);
  

Я думаю, что это должно быть как:

 boost::mutex::scoped_lock xyz(mutex_);
  

Как только я это сделаю, он подает жалобу на то, что mutex_ не является статическим. Почему это сработало в первую очередь, я не уверен. Изменение mutex_ на static приводит к ошибке связывания:

1> ThreadWorker.obj : ошибка LNK2001: неразрешенный внешний символ «private: статический класс boost:: mutex ThreadWorker:: mutex_» (?mutex_ @ThreadWorker@@0Vmutex@boost@@A) 1>c:somethingThreadTestDebugThreadTest.exe : неустранимая ошибка LNK1120: 1 неразрешенные внешние

Все еще играю с этим.

Комментарии:

1. Этот код не компилируется. Является mutex_ ли статический член в классе в реальном коде?

2. по static int i сути, это общая глобальная переменная, и она не защищена (ее защищают разные мьютексы …)

3. Функция статического класса не может получить доступ к нестатическому члену класса. FirstCount невозможно получить доступ mutex_ , если первая является статической функцией, а вторая — нестатическим членом.

4. Это вопрос для обсуждения, делаю ли я. Однако он инкапсулирует мьютекс.

5. @curiousguy Может быть, его причина заключалась в том, что ему нравится имя ThreadWorker, и поэтому он хочет создать объект и назвать его так. Может быть, он просто изучает потоки, думает, что они классные, и играет с ними, чтобы узнать, как они работают. Похоже на то, как вы были взволнованы, когда ребенок играл в песочнице и набил горсть песка в рот. Когда вы оглядываетесь на это сейчас, вы чувствуете себя грубым и глупым, но в тот момент это было что-то новое и захватывающее, и это было потрясающе, и никто не читал вам лекцию, чтобы объясниться. Его вопрос был ясен.

Ответ №1:

У вас две ошибки:

Прежде всего, как уже было замечено, mutex_ также должно быть статическим:

 private:
    static boost::mutex mutex_;
  

и, конечно, объявите его где-нибудь (желательно в файле .cpp!):

 boost::mutex ThreadWorker::mutex_{};
  

Теперь, почему компилятор не жалуется? Ну, потому что вы на самом деле не создаете ограниченную блокировку с аргументом mutex_ здесь:

 boost::mutex::scoped_lock(mutex_);
  

На самом деле это не вызовет нужный вам конструктор, а создаст (локальный) объект mutex_ , который имеет тип scoped_lock и создается конструктором по умолчанию. Следовательно, проблем с компилятором нет. Вы должны изменить его на что-то вроде следующего:

 boost::mutex::scoped_lock l{mutex_};
  

Теперь компилятор должен начать жаловаться на mutex_

Комментарии:

1. Это опечатка?: boost::mutex::scoped_lock l{mutex_}; Почему фигурные скобки? Также почему здесь фигурные скобки? boost::mutex ThreadWorker::mutex_{}; спасибо

2. Это не опечатка, это улучшенный синтаксис конструктора C 11, который предотвращает некоторые распространенные проблемы. Однако я не знаю, поддерживает ли Visual Studio это уже.

3. Интересно, я только что попробовал: int(j); и это работает, объявляя целое число j. Я не знал об этом. Немного страшно boost::mutex::scoped_lock(mutex_); только потому, что он молча примет это.

Ответ №2:

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

Возможно, вы также хотели сделать mutex_ статическим?

Редактировать: если я сделаю мьютекс статическим и удалю статику из переменной i, то, похоже, он работает так, как я предполагаю, что вы это имели в виду. Кажется, происходит что-то вроде этого: каждый поток немедленно входит в цикл и не блокируется друг от друга из-за того, что мьютекс не является статическим. К тому времени, когда все они выводятся на консоль (я не могу вспомнить, существует ли взаимное исключение при записи в cout), и i увеличивается, все они видят статическое значение i как 30 и завершают работу.

Редактировать 2: Нет, все еще неверно, поскольку в некоторых прогонах все еще есть вкрапленные значения.

Редактировать 3: Причина, по которой он компилируется, заключается в том, что ваш scoped_lock является временным, что, по-видимому, отвлекает компилятор от того факта, что mutex_ должен быть статическим. Попробуйте вместо этого следующий код:

 #include <iostream>
#include "boostthreadmutex.hpp"
#include "boostthreadthread.hpp"

class ThreadWorker
{
public:
    ThreadWorker() {}
    virtual ~ThreadWorker() {}

    static void FirstCount(int threadId)
    {
        // Created object f here rather than temprary
        boost::mutex::scoped_lock f(mutex_);
        int i = 0; // Not static

        for(i = 1; i <= 30; i  )
        {
            std::cout << i << ": Hi from thread:  " << threadId << std::endl;
        }

    }

private:
    static boost::mutex mutex_; // Static
};

// Storage for static
boost::mutex ThreadWorker::mutex_;

int main(int argc, char* argv[])
{
    boost::thread thread1(amp;ThreadWorker::FirstCount, 1);
    boost::thread thread2(amp;ThreadWorker::FirstCount, 2);
    boost::thread thread3(amp;ThreadWorker::FirstCount, 3);

    thread1.join();
    thread2.join();
    thread3.join();

    std::string input;
    std::cout << "Press <enter> to finish...n";
    std::getline( std::cin, input );
    return 0;
}
  

Комментарии:

1. Подумал то же самое, и я попытался сделать mutex_ статическим, но результат тот же.

2. Причина, по которой он проходит, заключается не в том, что компилятор оптимизирует временное значение (если бы он это сделал, многие RAII-коды потерпели бы неудачу). На самом деле он не создает временное значение, которое вы ожидаете от него создать.

3. 1 @tinman: спасибо за помощь в этом. В конечном счете, я думаю, что КиллианДС понял, что происходит, поэтому я выбрал его в качестве ответа

4. @KillianDS: спасибо, не знал, что это допустимый синтаксис для объявления переменной.

Ответ №3:

Этот код также компилируется с тем же компилятором?

Видите ли вы проблему с этим кодом? Попробуйте определить проблему, не компилируя ее.

класс C {
public:
static int f () {
возвращает i;
 }

 int i;
};

int main() {
возвращает C::f();
}

Обновить: Я только что прочитал ваше обновление.

класс C {
public:
static int f () {
возвращает i;
 }

 статический int i;
};

int C::i = что угодно;

int main() {
возвращает C::f();
}

Ответ №4:

Изменил мой код на:

ThreadWorker.h:

 #pragma once
#include "boostthreadmutex.hpp"
#include <iostream>

class ThreadWorker
{ 
public:
    ThreadWorker();
    virtual ~ThreadWorker();

    static void FirstCount(int threadId);

private:
    static boost::mutex mutex_;
};
  

ThreadWorker.cpp:

 #include "stdafx.h"
#include "ThreadWorker.h"

boost::mutex ThreadWorker::mutex_;

ThreadWorker::ThreadWorker()
{
}

ThreadWorker::~ThreadWorker()
{
}

void ThreadWorker::FirstCount(int threadId)
{
    boost::mutex::scoped_lock xyz(mutex_);
    static int i = 0;

    for(i = 1; i <= 30; i  )
    {
        std::cout << i << ": Hi from thread:  " << threadId << std::endl;
    }

}
  

ThreadTest.cpp:

 // ThreadTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "boostthreadthread.hpp"
#include "ThreadWorker.h"

int _tmain(int argc, _TCHAR* argv[])
{
    boost::thread thread1(amp;ThreadWorker::FirstCount, 1);
    boost::thread thread2(amp;ThreadWorker::FirstCount, 2);
    boost::thread thread3(amp;ThreadWorker::FirstCount, 3);

    thread1.join();
    thread2.join();
    thread3.join();

    std::string input;
    std::cout << "Press <enter> to finish...n";
    std::getline( std::cin, input );
    return 0;
}
  

Внесенное мной изменение состояло в том, чтобы 1. разделить заголовок и файл cpp (изначально я делал это только для того, чтобы сделать мой пост более компактным) 2. присвоить переменной scoped_lock идентификатор и 3. сделать мьютекс статическим.

Теперь он работает, как и ожидалось, печатая по 30 строк из каждого потока последовательно. Что все еще ставит меня в тупик, так это то, почему код, скомпилированный ранее (поскольку tinman также смог скомпилировать мой исходный код).

Комментарии:

1. @curiousguy: статический int был преднамеренным. Я хотел протестировать блокировку общего ресурса. Я не уверен, что вы имеете в виду класс ThreadWorker.

2. @User: код перестал компилироваться, как только я изменил scoped_lock. Как указал KillianDS, он создавал новую локальную переменную, не ссылающуюся на член mutex_ класса. Поскольку он никогда не ссылался на класс mutex_, компилятору не нужно было создавать код для доступа к нему из статического метода, и поэтому ошибки компилятора не было.