简体   繁体   中英

Destructor and thread safety

I want to create a thread safe class containing a method to insert elements into a list. When one of the threads destroys an instance, I want the messages in the list to be processed, while preventing other threads to insert other messages.

The idea is the following:

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

Is the synchronization correct?

For the method send I added the lock so that multiple threads can call it in a safe way.

As for what concerns the destructor, is there a possibility that a thread will call send between the lock release and the actual destruction of the instance? ie. is the for (and the following lock_guard destruction) the last instruction that will be executed before the actual destruction, or a race condition is possible once the destructor is executed?

You might split your class:

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, ...
};

And have a wrapper on it

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

So in main , you create an instance of std::shared_ptr<MyClass> . you pass it to each thread which wrap it in MyClassPerTHread .

When MyClassPerTHread is destroyed, You process the messages as expected.

You might want to adapt MyClassPerTHread for move/copy though.

You have a good intuition here; the lock_guard in the destructor isn't doing any good at all.

Here's why: The way this is written, any calls to send() must be done before ~MyClass() 's lock_guard is created – otherwise the message won't get processed and send() could very well be using m and my_list after their destruction is complete, leading to undefined behavior. Callers of send() have no way to make sure this happens other than just making sure all calls to send() are done before ~MyClass() even starts.

This is OK. Most classes have (or should have) a requirement that clients serialize destruction. That is, clients must make sure all callers to send() are done before ~MyClass() is invoked. In fact, all standard library classes have this requirement, unless otherwise documented. Some classes deliberately don't require this; that's fine, but somewhat exotic.

This really isn't terribly hard for clients to do, fortunately; they can just use a shared_ptr or something, as suggested by Jarod42.

tl;dr:

is there a possibility that a thread will call send between the lock release and the actual destruction of the instance?

Yes! Document that it is a client error if they do this and get rid of the lock in the destructor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM