您的位置:首页 > 其它

Deadlock: the problem and a solution

2016-01-10 03:08 232 查看
Deadlock Problem:

Each of a pair of threads needs to lock both of a pair of mutexes to perform some operation, and each thread has one mutex and is waitingfor the other. Neither thread can proceed, because
each is waiting for the other torelease its mutex. This scenario is called
deadlock, and it’s the biggest problem withhaving to lock two or more mutexes in order to perform an operation. 

class some_big_object; 

void swap(some_big_object& lhs,some_big_object& rhs);

class X

{

private:

    some_big_object some_detail;

    std::mutex m;

public:

    X(some_big_object const& sd):some_detail(sd){}

    friend void swap(X& lhs, X& rhs)

    {

if(&lhs==&rhs)
return; 

std::lock(lhs.m,rhs.m);

std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock);

std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock);

swap(lhs.some_detail,rhs.some_detail);



}; 

First, the arguments are checked to ensure they are different instances, becauseattempting to acquire a lock on astd::mutex
when you already hold it is undefinedbehavior. (A mutex that does permit multiple locks by the same thread is provided inthe form ofstd::recursive_mutex.
See section 3.3.3 for details.) Then, the call tostd::lock()Blocks
the two mutexes, and twostd::lock_guard
instances are con-structed, one for each mutex. Thestd::adopt_lock
parameter is supplied inaddition to the mutex to indicate to thestd::lock_guard
objects that the mutexesare already locked, and they should just adopt the ownership of the existing lock onthe mutex rather than attempt to lock the mutex in the constructor. 

This ensures that the mutexes are correctly unlocked on function exit in the gen-eral case where the protected operation might throw an exception; it also allows for asimple return. Also,
it’s worth noting that locking either lhs.mor
rhs.m inside the calltostd::lock
can throw an exception; in this case, the exception is propagated outofstd::lock.
Ifstd::lock
has successfully acquired a lock on one mutex and an 

exception is thrown when it tries to acquire a lock on the other mutex, this first lock isreleased automatically:std::lock
provides all-or-nothing semantics with regard tolocking the supplied mutexes. 

------------------------------------------------------------------------------------

Flexible locking with std::unique_lock

std::unique_lock
provides a bit more flexibility than
std::lock_guard
by relaxingthe invariants; a
std::unique_lock
instance doesn’t always own the mutex that it’sassociated with. First off, just as you can pass
std::adopt_lock
as a second argumentto the constructor to have the lock object manage the lock on a mutex, you can alsopass
std::defer_lock
as the second argument to indicate that the mutex shouldremain unlocked on construction. The lock can then be acquired later by callinglock()
on the
std::unique_lock
object (not
the mutex) or by passing the
std::unique_lock
object itself to
std::lock(). Listing 3.6 could just as easily have beenwritten as shown in listing 3.9, using
std::unique_lock
and
std::defer_lock
Brather than
std::lock_guard
and std::adopt_lock. The code has the same linecount
and is essentially equivalent, apart from one small thing: std::unique_locktakes more space and is a fraction slower to
use than std::lock_guar
a9e2
d. The flexibilityof allowing a
std::unique_lock
instance
not
to own the mutex comes at a price: thisinformation has to be stored, and it has to be updated. 

class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);


class X

{

private:

    some_big_object some_detail;

    std::mutex m;

public:

    X(some_big_object const& sd):some_detail(sd){}

    friend void swap(X& lhs, X& rhs)

    {

        if(&lhs==&rhs)

            return;

std::unique_lock<std::mutex> lock_a(lhs.m,std::defer_lock);
//mutexes are not locked here


std::unique_lock<std::mutex> lock_b(rhs.m,std::defer_lock); 

std::lock(lock_a,lock_b); swap(lhs.some_detail,rhs.some_detail);
// muteness are locked here

}

}; 

In listing 3.9, the
std::unique_lock
objects could be passed to
std::lock()
c
becausestd::unique_lock
provides
lock(),
try_lock(), and
unlock()
member functions.These forward to the member functions of the same name on the underlying mutexto do the actual work and just update a flag inside the
std::unique_lock
instance toindicate whether the mutex is currently owned by that instance. This flag is necessaryin order to ensure that
unlock()
is called correctly in the destructor. If the instancedoes
own the mutex, the destructor
must
call
unlock(), and if the instance
does not
ownthe mutex, it
must not
call
unlock(). This flag can be queried by calling the
owns_lock()member function. 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: