C++ 作为类成员的互斥体

C++ 作为类成员的互斥体,c++,multithreading,mutex,C++,Multithreading,Mutex,我试图创建一个简单的订户生产者模式,其中多个订户和一个生产者在不同的线程中运行(原因是为了了解更多关于线程的知识)。然而,我正在努力使互斥体成为生产者类的一员。代码如下: class Producer { private: vector<Subscriber* > subs; thread th; int counter; mutex mux; public: Producer() : counter(counter), th(&Pr

我试图创建一个简单的订户生产者模式,其中多个订户和一个生产者在不同的线程中运行(原因是为了了解更多关于线程的知识)。然而,我正在努力使互斥体成为生产者类的一员。代码如下:

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(); 
}

lock\u guard
的目的是防止生产者在向向量添加订户时同时通知向量中的订户。但是,在Project1.exe:0xC0000005:Access violation reading location 0x00000000中0x59963734(msvcp140d.dll)引发的notify
异常的锁保护中引发了此异常。
有人知道此异常的原因吗?如果将互斥设置为全局参数,则可以正常工作。也可以随意评论代码的其他问题。穿线对我来说是全新的

所以这里发生的是一个初始化顺序古怪

类成员是按照它们在类中声明的顺序构造的。在您的例子中,这意味着首先是订户向量,然后是线程,然后是计数器(!),最后是互斥。在构造函数中指定初始值设定项的顺序并不重要

但是!构造线程对象需要启动线程并运行其函数。这最终会导致使用互斥锁,可能是在
生产者
构造函数实际初始化互斥锁之前。因此,您最终使用了一个尚未构造的互斥体,以及一个尚未初始化的计数器(这并不是问题的原因)

一般来说,每当有成员初始值设定项提到
this
或其他类成员(包括调用成员函数)时,都应该小心。它设置用于访问未初始化对象的场景


在您的情况下,只需将互斥和计数器成员移动到线程成员之前就足够了。

我只想将此添加到@Sneftel给出的答案中,以供参考:

根据CPP标准(N4713),突出显示了相关部分:

15.6.2初始化基和成员[class.base.init]

13在非委托构造函数中,初始化按以下顺序进行:
(13.1)-首先,并且仅对于派生类最多的构造函数(6.6.2),虚拟基类在 它们在基类的有向无环图的深度优先的从左到右遍历中出现的顺序, 其中“从左到右”是派生类基类说明符列表中基类的出现顺序。
(13.2)-然后,直接基类按照它们出现在基说明符列表中的声明顺序初始化 (不考虑mem初始值设定者的顺序)。
(13.3)-然后,按照类定义中声明的顺序初始化非静态数据成员 (同样,不考虑mem初始值设定者的顺序)。
(13.4)-最后,执行构造函数主体的复合语句。
[注意:声明顺序是为了确保基和成员子对象按与初始化相反的顺序销毁。-结束说明]


要做的第一件事是使用调试器运行它,并在调用完成后检查调用堆栈。此外,您还应该提供一个,您发布的代码几乎就是一个。
Producer():counter(counter)
在我看来很可疑。我不确定,但这可能是未定义的,因为您使用的是代码>计数器< /C>未初始化,因此超出该错误的任何东西都可能出错:“初始化顺序古怪”:)我只喜欢C++中的术语来命名它的所有。quirks@user463035818的确C++需要一个只需要描述与初始化顺序有关的怪癖。
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;
}