文章目录
1 前言
常用的线程间同步/通信(IPC)方式有锁(互斥锁、读写锁、自旋锁)、屏障、条件变量、信号量、消息队列。其中锁一种最常用的一种IPC,用于对多个线程共享的资源进行保护,达到线程互斥访问资源的目的。以互斥锁为例,其中最常见的异常而且是致命的问题是——“死锁”。
死锁(DeadLock) 是指两个或者两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,导致两者的任务都无法继续执行下去,直至重新执行程序。
基于RAII(Resource Acquisition Is Initialization)设计理念,C++ 11引入了lock_guard
和unique_lock
类模板,以尽可能避免死锁产生。
2 lock_guard
lock_guard是C++ 11引入的一个互斥锁类模板。lock_guard基于RAII设计理念,将互斥锁的作用范围和(对象)作用域绑定,函数退出作用域后,自动释放锁资源。避免忘记解锁造成的死锁现象。
template <class Mutex> class lock_guard;
lock_guard
具有如下特点:
- 创建
lock_guard
对象,即获取互斥锁权限,并上锁 - 作用域中途不能解锁
- 退出
lock_guard
对象作用域后,自动解锁 -
lock_guard
锁不能复制且不能移动,禁止拷贝构造和移动构造
3 lock_guard使用
lock_guard
使用比较简单:
- 首先需要包含
mutex
头文件 - 然后创建一个锁实例
mutex
- 在需要加锁的作用域内,创建以锁示例
mutex
作为形参的lock_guard
对象
伪代码实现过程:
/* for std::mutex std::lock_guard */
std::mutex mutex;
void func(void)
{
const std::lock_guard<std::mutex> lock(mutex);
/* todo,上锁区域;无需手动解锁,*/
}
写个例子,代码实现功能:
- 创建两个线程
- 线程分别对全局变量访问,并输出到终端
- 期望结果,线程1输出结果“ 1 2 3 4 5”,线程2输出结果“5 4 3 2 1”
/* 是否使用互斥锁,使用,0不使用 */
std::mutex s_mutex;
static int8_t g_count = 0;
void *thread0_entry(void *data)
{
uint8_t i =0;
const std::lock_guard<std::mutex> lock(s_mutex);
for (i = 0;i < 5;i++)
{
g_count ++;
printf("%d ", g_count);
usleep(100);
}
printf("\r\n");
}
void *thread1_entry(void *data)
{
uint8_t i =0;
const std::lock_guard<std::mutex> lock(s_mutex);
for (i = 0;i < 5;i++)
{
printf("%d ", g_count);
g_count--;
usleep(100);
}
printf("\r\n");
}
int main(int argc, char **argv)
{
pthread_t thread0;
pthread_t thread1;
void *retval;
pthread_create(&thread0, NULL, thread0_entry, NULL);
pthread_create(&thread1, NULL, thread1_entry, NULL);
pthread_join(thread0, &retval);
pthread_join(thread1, &retval);
return 0;
}
分别编译使用互斥锁和不使用互斥锁版本;加锁版本输出结果正确。
acuity@ubuntu:/home/RAII$ g++ lock.cpp -o lock -lpthread -std=c++11
acuity@ubuntu:/home/RAII$ ./lock
1 2 3 4 5
5 4 3 2 1
acuity@ubuntu:/home/RAII$ g++ lock.cpp -o lock -lpthread -std=c++11
acuity@ubuntu:/home/RAII$ ./lock
1 1 0 1 0 0 1 1 0 0
4 unique_lock
unique_lock 是 lock_guard
的衍生版,除了具备lock_guard
的完整功能,还增加了自身特有的功能,以适应一些lock_guard
无法实现的复杂加锁场景。与lock_guard
,相比,unique_lock
增加的特性包括:
- 任意时候上锁(指定第二个参数为
std::defer_lock
),非创建即上锁
void fun0(void)
{
std::unique_lock<std::mutex> ulock(mutex, std::defer_lock); /* 创建对象,不上锁 */
/* todo */
guard.lock(); /* 上锁 */
/* 退出作用域,自动解锁 */
}
- 提供解锁接口
unlock
,可以中途解锁,非等退出作用域后才解锁
void fun1(void)
{
std::unique_lock<std::mutex> ulock(mutex);
/* todo */
guard.unlock(); /* 解锁 */
/* todo */
guard.lock(); /* 继续上锁 */
/* 退出作用域,自动解锁 */
}
- 不可复制,但可移动
/* lock_guard 不可复制,不可移动 */
std::lock_guard<std::mutex> lock0(mutex);
std::lock_guard<std::mutex> lock1 = lock0; /* error */
std::lock_guard<std::mutex> lock1 = std::move(lock0); /* error */
/* unique_lock 不可复制,可以移动 */
std::unique_lock<std::mutex> ulock0(mutex);
std::unique_lock<std::mutex> ulock1 = ulock0; /* error */
std::unique_lock<std::mutex> ulock1 = std::move(ulock0); /* ok */
使用原则:
lock_guard
使用简单,效率高;unique_lock
使用比较灵活,效率比lock_guard
稍微低一点,因为其内部需要维护锁的状态。关于选择使用原则:lock_guard
能解决问题的时候,选择lock_guard
;否则选择unique_lock
。
注意:lock_guard
和unique_lock
只支持STL的mutex,不支持POSIX标准的mutex(pthread_mutex_t)。至少目前未支持,编译会失败。
5 相关文章
【1】RAII在C++编程中的必要性