C++ 析构函数与线程安全

C++ 析构函数与线程安全,c++,synchronization,destructor,C++,Synchronization,Destructor,我想创建一个线程安全类,其中包含一个将元素插入列表的方法。 当其中一个线程销毁实例时,我希望处理列表中的消息,同时防止其他线程插入其他消息 其思路如下: MyClass{ ... public: ... void send(string s){ lock_guard<mutex> lock(m); my_list.push_back(s); } ~MyClass(){

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

其思路如下:

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);
      }
}
MyClass{
...
公众:
...
无效发送(字符串s){
锁和防护锁(m);
我的列表。向后推;
}
~MyClass(){
锁和防护锁(m);
用于(字符串s:我的列表)
过程;
}
}
同步是否正确

对于方法
send
,我添加了锁,以便多个线程可以安全地调用它


至于与析构函数有关的是什么,在锁释放和实例的实际销毁之间,线程是否有可能调用
send
?即的
(以及下面的
锁定保护
销毁)是实际销毁之前执行的最后一条指令,还是执行销毁程序后可能出现竞争条件

您可以拆分您的类:

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-MyClass
{
公众:
void send(const std::string&s){
锁和防护锁(m);
我的列表。向后推;
}
作废进程\所有\消息()
{
锁和防护锁(m);
for(常量字符串&s:我的列表)
过程;
//我的清单。清除();
}
无效过程(const std::string&s);
//…互斥,列表。。。
};
还有一个包装

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;
};
类MyClassPerTHread
{
公众:
显式MyClassPerTHread(std::Sharedptrptr):ptr(ptr){}
~MyClassPerTHread(){ptr->process_all_messages();}
//MyClassPerTHread(constmyclassperthread&);
//MyClassPerTHread(MyClassPerTHread&&);
//MyClassPerTHread&运算符=(const MyClassPerTHread&);
//MyClassPerTHread&运算符=(MyClassPerTHread&&);
void send(const std::string&s){ptr->send(s);};
私人:
std::共享的ptr;
};
因此,在
main
中,您创建了
std::shared\u ptr
的一个实例。 您将它传递给每个线程,这些线程将它包装在
MyClassPerTHread

MyClassPerTHread
被销毁时,您将按预期处理消息


不过,您可能需要将MyClassPerTHread改编为移动/复制;析构函数中的
lock\u-guard
没有任何作用

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

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

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

tl;博士:

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


对!!记录如果他们这样做并清除析构函数中的锁,则这是一个客户端错误。

如果您在析构函数中,您应该能够假设没有人可以使用任何类型的指针或对该
的引用,否则他们将使用悬空指针/引用。析构函数的末尾没有发生任何特殊情况,阻止某人调用
send
。换句话说,如果有可能调用
send
,您不应该在析构函数中。@FrançoisAndrieux:这可能是消息的目标:删除其他类中对self的引用(unsubscribe observer,…。@Jarod42则存在设计问题。当对象的一个成员函数可能正在运行时,该对象将被销毁。问题是,互斥锁不会阻止任何人调用send,它只会阻止函数体移动到第一行之外。最终,该互斥锁将解锁,执行将继续,不管是否还有
。您必须以某种方式中止函数调用,但不能以任何方式依赖
this
。您必须对可能被调用的所有其他成员函数执行相同的操作。“for(string s:my_list)”应该是:“for(string&s:my_list)”,这样它就不会产生无用的副本。您可以通过让客户端直接在std::shared_ptr上操作来简化这一过程。这是惯用用法,具有MyClassPerThread所具有的所有优点。shared_ptr还具有较少争用的优点,因为处理_all_消息()的调用较少。@ech:据我所知,OP希望在
shared_ptr
的ref计数器每次减少时都进行回调,因此建议的split.interest–是的,如果您想要这种行为,那么-perthread类是有意义的。