Search code examples
c++mutexstdthreaddetachstdmutex

Destructor, when object's dynamic variable is locked by mutex will not free it?


I'm trying to solve some complicated (for me at least) asynchronous scenario at once, but I think it will be better to understand more simple case.

Consider an object, that has allocated memory, carrying by variable:

#include <thread>
#include <mutex>
using namespace std;

mutex mu;

class Object
{
public:
  char *var;

  Object()
  {
      var = new char[1]; var[0] = 1;
  }
  ~Object()
  {
      mu.lock();
      delete[]var; // destructor should free all dynamic memory on it's own, as I remember
      mu.unlock();
  }
}*object = nullptr;

int main()
{
   object = new Object();

   return 0;
}

What if while, it's var variable in detached, i.e. asynchronous thread, will be used, in another thread this object will be deleted?

void do_something()
{
    for(;;)
    {
         mu.lock();
         
         if(object)
             if(object->var[0] < 255)
                  object->var[0]++;
             else
                  object->var[0] = 0;

         mu.unlock();
    }
}

int main()
{
   object = new Object();

   thread th(do_something);
   th.detach();

   Sleep(1000);

   delete object;
   object = nullptr;

   return 0;
}
  1. Is is it possible that var will not be deleted in destructor?
  2. Do I use mutex with detached threads correctly in code above?

2.1 Do I need cover by mutex::lock and mutex::unlock also delete object line?

I also once again separately point that I need new thread to be asynchronous. I do not need the main thread to be hanged, while new is running. I need two threads at once.


P.S. From a list of commentaries and answers one of most important thing I finally understood - mutex. The biggest mistake I thought is that already locked mutex skips the code between lock and unlock.

Forget about shared variables, mutex itself has noting to do with it. Mutex is just a mechanism for safely pause threads:

mutex mu;

void a()
{
    mu.lock();
    Sleep(1000);
    mu.unlock();
}

int main()
{
    thread th(a);
    th.detach();

    mu.lock(); // hangs here, until mu.unlock from a() will be called
    mu.unlock();

    return;
}

The concept is extremely simple - mutex object (imagine) has flag isLocked, when (any) thread calls lock method and isLocked is false, it just sets isLocked to true. But if isLocked is true already, mutex somehow on low-level hangs thread that called lock until isLocked will not become false. You can find part of source code of lock method scrolling down this page. Instead of mutex, probably just a bool variable could be used, but it will cause undefined behaviour.

Why is it referred to shared stuff? Because using same variable (memory) simultaneously from multiple threads makes undefined behaviour, so one thread, reaching some variable that currently can be used by another - should wait, until another will finish working with it, that's why mutex is used here.

Why accessing mutex itself from different threads does not make undefined behaviour? I don't know, going to google it.


Solution

    1. Is is it possible that var will not be deleted in destructor?

    With

    ~Object()
    {
        mu.lock();
        delete[]var; // destructor should free all dynamic memory on it's own, as I remember
        mu.unlock();
    }
    

    You might have to wait that lock finish, but var would be deleted.

    Except that your program exhibits undefined behaviour with non protected concurrent access to object. (delete object isn't protected, and you read it in your another thread), so everything can happen.

    1. Do I use mutex with detached threads correctly in code above?

    Detached or not is irrelevant.

    And your usage of mutex is wrong/incomplete. which variable should your mutex be protecting?

    It seems to be a mix between object and var. If it is var, you might reduce scope in do_something (lock only in if-block)

    And it misses currently some protection to object.

    2.1 Do I need cover by mutex::lock and mutex::unlock also delete object line?

    Yes object need protection.

    But you cannot use that same mutex for that. std::mutex doesn't allow to lock twice in same thread (a protected delete[]var; inside a protected delete object) (std::recursive_mutex allows that).

    So we come back to the question which variable does the mutex protect?

    if only object (which is enough in your sample), it would be something like:

    #include <thread>
    #include <mutex>
    using namespace std;
    
    mutex mu;
    
    class Object
    {
    public:
      char *var;
    
      Object()
      {
          var = new char[1]; var[0] = 1;
      }
      ~Object()
      {
          delete[]var; // destructor should free all dynamic memory on it's own, as I remember
      }
    }*object = nullptr;
    
    void do_something()
    {
        for(;;)
        {
             mu.lock();
             
             if(object)
                 if(object->var[0] < 255)
                      object->var[0]++;
                 else
                      object->var[0] = 0;
    
             mu.unlock();
        }
    }
    
    int main()
    {
       object = new Object();
    
       thread th(do_something);
       th.detach();
    
       Sleep(1000);
    
       mu.lock(); // or const std::lock_guard<std::mutex> lock(mu); and get rid of unlock
       delete object;
       object = nullptr;
       mu.unlock();
    
       return 0;
    }
    

    Alternatively, as you don't have to share data between thread, you might do:

    int main()
    {
       Object object;
    
       thread th(do_something);
    
       Sleep(1000);
    
       th.join();
       return 0;
    }
    

    and get rid of all mutex