我想创建一个线程安全类,其中包含将元素插入列表的方法。 当其中一个线程破坏一个实例时,我希望处理列表中的消息,同时防止其他线程插入其他消息。
想法如下:
MyClass{
...
public:
...
void send(string s){
lock_guard<mutex> lock(m);
my_list.push_back(s);
}
~MyClass(){
lock_guard<mutex> lock(m);
for(string s:my_list)
process(s);
}
}
同步是否正确?
对于方法send
,我添加了锁,以便多个线程可以安全的方式调用它。
关于析构函数,线程是否有可能在锁定释放和实例的实际破坏之间调用send
?即。 for
(及其后的lock_guard
销毁)是在实际销毁之前将要执行的最后一条指令,还是一旦执行了析构函数就可能出现竞争条件?
答案 0 :(得分:1)
您可以分割课程:
class MyClass
{
public:
void send(const std::string& s){
lock_guard<mutex> lock(m);
my_list.push_back(s);
}
void process_all_messages()
{
lock_guard<mutex> lock(m);
for (const string& s : my_list)
process(s);
//my_list.clear();
}
void process(const std::string& s);
// ... mutex, list, ...
};
上面有一个包装纸
class MyClassPerTHread
{
public:
explicit MyClassPerTHread(std::shared_ptr<MyClass> ptr) : ptr(ptr) {}
~MyClassPerTHread(){ ptr->process_all_messages(); }
// MyClassPerTHread(const MyClassPerTHread&);
// MyClassPerTHread(MyClassPerTHread&&);
// MyClassPerTHread& operator=(const MyClassPerTHread&);
// MyClassPerTHread& operator=(MyClassPerTHread&&);
void send(const std::string& s) { ptr->send(s); };
private:
std::shared_ptr<MyClass> ptr;
};
因此,在main
中,您将创建std::shared_ptr<MyClass>
的实例。
您将其传递给每个将其包装在MyClassPerTHread
中的线程。
MyClassPerTHread
被销毁后,您将按预期处理消息。
您可能想使MyClassPerTHread
适应移动/复制。
答案 1 :(得分:0)
您在这里有很好的直觉;析构函数中的lock_guard
根本没有任何好处。
原因如下:
编写方式上,必须在创建send()
的lock_guard之前完成对~MyClass()
的所有调用-否则消息将不会得到处理,send()
可能会使用m和my_list销毁完成后,将导致不确定的行为。 send()
的调用者无法确保发生这种情况,只能确保甚至在send()
开始之前就已完成对~MyClass()
的所有调用。
这可以。大多数类具有(或应该具有)客户端序列化销毁的要求。也就是说,客户端必须确保在调用send()
之前完成对~MyClass()
的所有调用者。实际上,除非另有说明,否则所有标准库类都具有此要求。有些课程故意不要求这样做。很好,但有点异国情调。
幸运的是,对于客户而言,这确实不是一件难事。他们可以使用Jarod42建议的shared_ptr之类的东西。
tl; dr:
线程是否有可能在锁定释放和实例的实际破坏之间调用send?
是的!如果这样做并摆脱析构函数中的锁定,请记录这是客户端错误。