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 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.
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.
相关文章推荐
- 手机验证码设计和防刷制度
- 解读大型网站的演变过程
- NO.90 Struts2.3.15.1升级总结
- BugPhobia开发终结篇章:Beta阶段第XI次Scrum Meeting
- 解读大型网站的演变过程
- 悦苗园公益活动
- iOS 关于颜色的库 - Wonderful
- 【Beta阶段】测试报告
- 【Beta阶段】M2事后分析
- VirtualBox Unable to load R3 module 问题解决方案
- 悦苗园公益活动
- 关于MAC下Android SDK manager 更新解决办法(无需翻墙)
- 程序猿打招自己的电子图书馆
- Java学习之自定义异常
- 最小圆覆盖模板
- SNMP PDU解析
- iframe去边框,无边框
- php 合并数组的方法 非array_merge
- java中垃圾回收(gc)
- 程序猿打招自己的电子图书馆