Mutex как член класса

спросил: 31 июля 2018 в 09:42 в: c++

Я пытаюсь создать простой шаблон продления подписчика, когда несколько подписчиков и один продюсер работают в разных потоках (причина в том, чтобы узнать больше о потоковой передаче). Однако я стараюсь сделать мьютекс членом класса продюсера. Код приведен ниже:

class Producer {
private:
    vector<Subscriber* > subs;
    thread th;
    int counter;
    mutex mux;public:
    Producer() : counter(counter), th(&Producer::run, this) {};
    void addSubscriber(Subscriber* s);
    void notify();
    void incrementCounter();
    void run();
    void callJoin(){th.join(); } 
};void Producer::run() {
    for (int i = 0; i < 10; i++) {
        incrementCounter();
        this_thread::sleep_for(std::chrono::milliseconds(3000));
    }
}void Producer::addSubscriber(Subscriber* s) {
    lock_guard<mutex> lock(mux);
    subs.push_back(s); 
}void Producer::notify() {
    lock_guard<mutex> lock(mux);
    for (auto it = subs.begin(); it != subs.end(); ++it) {
        (*it)->setCounterCopy(counter);
    }
}void Producer::incrementCounter() {
    counter++;
    notify(); 
}

Класс подписчика:

class Subscriber {
private:
    string name;
    thread th;
    atomic<int> counterCopy = 0;public:    Subscriber(string name) : name(name),  th(&Subscriber::run, this) {};
    void run() {
        while (true) {
            cout << name << ": " << counterCopy << endl; 
            this_thread::sleep_for(std::chrono::milliseconds(1000));            
        }
    }
    void callJoin() { th.join(); }
    void setCounterCopy(int counterCopy) { this->counterCopy = counterCopy; };
};

В основном:

int main() {
    Producer p;
    Subscriber s1("Sub1");
    p.addSubscriber(&s1);
    s1.callJoin();
    p.callJoin();
    return 0;
}

Цель lock_guard заключается в том, чтобы запретить производителю уведомлять подписчиков в векторе одновременно, когда абонент добавляется к вектору. Однако это исключение бросается в lock_guard извещения Exception thrown at 0x59963734 (msvcp140d.dll) in Project1.exe: 0xC0000005: Access violation reading location 0x00000000. Кто-нибудь знает, что может быть причиной этого исключения? Если mutex установлен как глобальный параметр, это работает отлично. Не стесняйтесь комментировать другие проблемы кода. Threading для меня новичок.

2 ответа

Есть решение
Sneftel ответил: 31 июля 2018 в 09:51

То, что здесь происходит, это дурацкий порядок инициализации.

Члены класса создаются в том порядке, в котором они объявлены в классе. В вашем случае это означает сначала вектор подписчиков, затем поток, затем счетчик (!) И, наконец, мьютекс. Порядок, в котором вы указываете инициализаторы в конструкторе, не имеет значения.

Но! Создание объекта потока влечет за собой запуск потока, выполнение его функции. И это, в конечном итоге, приводит к использованию мьютекса, возможно, до того, как конструктор Producer дойдет до точки, где он фактически его инициализирует. Таким образом, вы заканчиваете тем, что используете еще не сконструированный мьютекс и (не то, что это причина вашей проблемы) и еще не инициализированный счетчик.

В общем, вы должны быть осторожны, когда у вас есть инициализатор члена, в котором упоминается this или другой член класса. Он устанавливает сцену для доступа к неинициализированному объекту.

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

user463035818 ответил: 31 июля 2018 в 09:54
"дурацкий порядок инициализации" :) Я люблю c ++ только за термины, которые нужно придумать, чтобы назвать все его причуды
Sneftel ответил: 31 июля 2018 в 09:56
@ user463035818 Действительно, C ++ требует, чтобы у него был богатый словарный запас, просто чтобы описать причуды , относящиеся к порядку инициализации .
P.W ответил: 31 июля 2018 в 09:59

Я просто хочу добавить это к данному ответу @Sneftel для справки:

Из стандарта CPP (N4713) соответствующая часть выделена:

15.6.2 Инициализация баз и членов [class.base.init]

...

13 В неделегативном режиме Конструктор, инициализация происходит в следующем порядке:
(13.1) - Во-первых, и только для конструктора самого производного класса (6.6.2), виртуальные базовые классы инициализируются в том порядке, в котором они отображаются слева на глубине. обход направленного ациклического графа базовых классов справа налево, где "слева направо" - порядок появления базовых классов в списке базовых спецификаторов производного класса.
(13.2) - Затем прямой базовые классы инициализируются в порядке объявления по мере их появления в списке базовых спецификаторов (независимо от порядка mem-инициализаторов).
(13.3) - Затем элементы нестатических данных инициализируются в порядок, в котором они были объявлены в определении класса (снова независимо от f порядка mem-инициализаторов).
(13.4) - Наконец, составной оператор тела конструктора выполняется.
[Примечание: порядок декларирования должен гарантировать, что база и подобъекты-члены уничтожаются в обратном порядке инициализации. - конец заметки]