C语言
已知在一个多线程的环境,有一个结构体用于计数。
struct counter
{
pthread_mutex_t m;
int cnt;
} counter_t;
其中 m 是互斥锁,用于保证数据 cnt 的排他性访问。
当我们需要交换同一个结构体的两个对象时,很容易就会写出下面的代码。
void counter_swap( counter_t *a, counter *b )
{
int tmp;
pthread_mutex_lock( a->m ); /* 上锁 */
pthread_mutex_lock( b->m );
tmp = a->cnt;
a->cnt = b->cnt;
b->cnt = tmp;
pthread_mutex_unlock( b->m ); /* 解锁 */
pthread_mutex_unlock( a->m );
}
如果一条线程执行 counter_swap( x, y )
的同时,另一条执行 counter_swap( y, x )
就会发生死锁。
为了避免死锁,该函数需要如下修改:
void counter_swap( counter_t *a, counter *b )
{
int tmp;
if( a == b ) {
return;
}
else if( a < b ) {
pthread_mutex_lock( a->m ); /* 先锁低地址 */
pthread_mutex_lock( b->m );
tmp = a->cnt;
a->cnt = b->cnt;
b->cnt = tmp;
pthread_mutex_unlock( b->m );
pthread_mutex_unlock( a->m );
}
else {
pthread_mutex_lock( b->m ); /* 先锁低地址 */
pthread_mutex_lock( a->m );
tmp = a->cnt;
a->cnt = b->cnt;
b->cnt = tmp;
pthread_mutex_unlock( a->m );
pthread_mutex_unlock( b->m );
}
}
一个函数如果要锁住相同类型的多个对象,为了保证始终按相同的顺序加锁,我们可以比较mutex对象的地址,始终先加锁地址比较小的mutex。
C++
C++ 相比 C 就方便很多:
- C++ 有 RAII 机制的 guard,锁上后可以自动解锁
- C++ 的 std::lock() 函数确保了对多个mutex上锁而不死锁
- C++ 有std::swap() 函数,无需自己交换
class counter
{
public:
std::mutex m;
int cnt;
};
void counter_swap( counter &a, counter &b )
{
std::lock( a.m, b.m );
std::lock_guard<std::mutex> guard1( a.m, std::adopt_lock );
std::lock_guard<std::mutex> guard2( b.m, std::adopt_lock );
std::swap<int>( a.cnt, b.cnt );
}