互斥体作为类的成员

Mutex as member of class

本文关键字:成员      更新时间:2023-10-16

我正在尝试制作一个简单的订阅者-生产者模式,其中多个订阅者和一个生产者在不同的线程中运行(原因是要了解有关线程的更多信息(。然而,我正在努力使互斥锁成为生产者类的一员。代码给出如下:

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的目的是防止生产者在将订阅者添加到矢量时同时通知矢量中的订阅者。但是,此例外被抛在通知Exception thrown at 0x59963734 (msvcp140d.dll) in Project1.exe: 0xC0000005: Access violation reading location 0x00000000.的lock_guard 有谁知道此例外的原因可能是什么?如果将互斥锁设置为全局参数,则工作正常。也请随意评论代码的其他问题。线程对我来说是全新的。

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

类成员按照它们在类中声明的顺序构造。在您的情况下,这意味着首先是订阅者的向量,然后是线程,然后是计数器 (!(,最后是互斥锁。在构造函数中指定初始值设定项的顺序无关紧要。

但!构造线程对象需要启动线程,运行其函数。这最终会导致互斥锁被使用,可能在Producer构造函数达到实际初始化它的位置之前。因此,您最终使用尚未构造的互斥锁,并且(不是问题的原因(还有一个尚未初始化的计数器。

通常,只要成员初始值设定项提到this或其他类成员,包括调用成员函数,就应该保持警惕。它设置访问未初始化对象的场景。

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

我只想通过@Sneftel将其添加到给定的答案中以供参考:

从CPP标准(N4713(中,突出显示了相关部分:

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

13

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