析构函数和线程安全

Destructor and thread safety

本文关键字:安全 线程 析构函数      更新时间:2023-10-16

我想创建一个线程安全类,其中包含将元素插入列表的方法。 当其中一个线程销毁实例时,我希望处理列表中的消息,同时防止其他线程插入其他消息。

思路如下:

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销毁(是在实际销毁之前执行的最后一个指令,还是在执行析构函数后可能会出现竞争条件?

你可以拆分你的类:

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以进行移动/复制。

你在这里有一个很好的直觉;析构函数中的lock_guard根本没有任何好处。

原因如下: 按照这种编写方式,任何对send()的调用都必须在创建~MyClass()lock_guard之前完成——否则消息将无法得到处理,send()很可能在销毁完成后使用 m 和 my_list,从而导致未定义的行为。send()的调用者无法确保这种情况发生,只能确保在~MyClass()开始之前完成对send()的所有调用。

这没关系。大多数类都有(或应该有(客户端序列化销毁的要求。也就是说,客户端必须确保在调用~MyClass()之前完成send()的所有调用方。事实上,除非另有说明,否则所有标准库类都有此要求。有些类故意不需要这样做;这很好,但有些异国情调。

幸运的是,这对客户来说并不难做到;他们可以使用shared_ptr或其他东西,正如Jarod42所建议的那样。

tl;博士:

线程是否有可能在锁定释放和实例的实际销毁之间调用 send ?

是的!记录如果他们这样做并摆脱析构函数中的锁,这是客户端错误。