rohitsan
rohitsan

Reputation: 1041

Can deadlock occur in this code snippet and why?

Is deadlock possible in the following code snippet:

void f()
{
    {
        std::lock_guard <std::mutex> inner (lock1);
        // do something and conditionally return
    }
    std::lock_guard <std::mutex> outer (lock2);
    // do something
}

IOW, if multiple threads call this function, can a deadlock occur?

I am not too sure so any help would be highly appreciated.

Upvotes: 1

Views: 274

Answers (4)

mfnx
mfnx

Reputation: 3018

The answer is no. A fast explanation is that the locks are always acquired in the same order and there is no repetition (first lock1, then lock2 if no return takes place).

Each thread first holds lock1. Whether the thread then conditionally returns or not does not matter. It does not try to acquire lock2 while lock1 is still held. In other words, lock1 is always unlocked by a thread before that same thread tries to acquire lock2.

As @Gupta suggested, here is a link which describes how deadlocks can be avoided by locking in a predefined order.

Upvotes: 3

Clonk
Clonk

Reputation: 2070

If you refactor your code so each scope is a function, it become clears that the locks are never locked at the same time by a single thread :

std::mutex lock1;
std::mutex lock2;

// One mutex in g => No deadlock possible
void g()
{
    std::lock_guard <std::mutex> inner (lock1);
    // do something
}

// One mutex in h => No deadlock possible
void h()
{
    std::lock_guard <std::mutex> outer (lock2);
    // do something 
} 

// No mutex in f => No deadlock possible
void f()
{
    g();
    h();
}

From this you can conclude that when a thread is a requesting a lock, it does not hold one. This makes deadlock impossible. You can check this yourself by creating a BasicLockable object that simply wrap std::mutex and add a trace :

class PrinterMutex {
  public:
  PrinterMutex(const std::string& _name) : name(_name) {}
  ~PrinterMutex() {}
  void lock() {
    std::cout << "lock : " << name << std::endl;
    m.lock();
  }
  void unlock() {
    std::cout << "unlock : " << name << std::endl;
    m.unlock();
  }
  private:
    std::mutex m;
    std::string name;
};

PrinterMutex lock1("lock1");
PrinterMutex lock2("lock2");

int main()
{
    {
        std::lock_guard <PrinterMutex> inner (lock1);
        // do something and conditionally return
    }
    std::lock_guard <PrinterMutex> outer (lock2);
    // do something
} 

The trace will show you that a thread will always release a lock before requesting one, making a deadlock impossible.

If you actually need multiple mutex in your code, you should use std::lock with multiple Lockable object to lock mutexes with deadlock avoidance algorithm.

std::mutex lock1;
std::mutex lock2;
void g()
{
        std::lock(lock1, lock2);
        std::lock_guard<std::mutex> inner (lock1, std::adopt_lock);
        std::lock_guard<std::mutex> outer (lock2, std::adopt_lock);
        // Do something
}

Upvotes: 5

Erlkoenig
Erlkoenig

Reputation: 2754

If the only two accesses to your two locks (lock1, lock2) are the ones shown in your code snippet (i.e. the locks are never accessed anywhere else), a deadlock can't happen, as the two locks are never locked simultaneously.

If the pair of braces around the inner lock guard weren't there, the two locks would be locked simultaneously, but since the order is always the same (first lock1, then lock2) deadlocks would still be impossible.

However, if f were to call some other function that might lock some other lock (especially via function pointer or virtual functions), or f would call itself recursively, a deadlock could happen (depending on the exact code).

Upvotes: 1

Sergey Strukov
Sergey Strukov

Reputation: 401

I see no reason this code can produce a deadlock.

Upvotes: 0

Related Questions