进行多线程编程,如果多个线程需要对同一块内存进行操作,比如:同时读、同时写、同时读写
对于后两种情况来说,如果不做任何的人为干涉就会出现各种各样的错误数据。这是因为线程在运行的时候需要先得到CPU时间片,时间片用完之后需要放弃已获得的CPU资源,就这样线程频繁地在就绪态和运行态之间切换,更复杂一点还可以在就绪态、运行态、挂起态之间切换,这样就会导致线程的执行顺序并不是有序的,而是随机的混乱的,就如同下图中的这个例子一样,理想很丰满现实却很残酷。
解决多线程数据混乱的方案就是进行线程同步,最常用的就是互斥锁,在C++11中一共提供了四种互斥锁:
std::mutex
:独占的互斥锁,不能递归使用std::timed_mutex
:带超时的独占互斥锁,不能递归使用std::recursive_mutex
:递归互斥锁,不带超时功能std::recursive_timed_mutex
:带超时的递归互斥锁
互斥锁在有些资料中也被称之为互斥量,二者是一个东西。
如果对线程同步还一无所知,建议先看一下这篇文章:
1. std::mutex
不论是在C还是C++中,进行线程同步的处理流程基本上是一致的,C++的mutex类提供了相关的API函数:
1.1 成员函数
lock()
函数用于给临界区加锁,并且只能有一个线程获得锁的所有权
,它有阻塞线程的作用,函数原型如下:
void lock();
独占互斥锁对象有两种状态:锁定
和未锁定
。如果互斥锁是打开的,调用lock()
函数的线程会得到互斥锁的所有权,并将其上锁,其它线程再调用该函数的时候由于得不到互斥锁的所有权,就会被lock()
函数阻塞。当拥有互斥锁所有权的线程将互斥锁解锁,此时被lock()
阻塞的线程解除阻塞,抢到互斥锁所有权的线程加锁并继续运行,没抢到互斥锁所有权的线程继续阻塞。
除了使用lock()
还可以使用try_lock()
获取互斥锁的所有权并对互斥锁加锁,函数原型如下:
bool try_lock();
二者的区别在于try_lock()
不会阻塞线程,lock()
会阻塞线程:
- 如果互斥锁是未锁定状态,得到了互斥锁所有权并加锁成功,函数返回true
- 如果互斥锁是锁定状态,无法得到互斥锁所有权加锁失败,函数返回false
当互斥锁被锁定之后可以通过unlock()
进行解锁,但是需要注意的是只有拥有互斥锁所有权的线程也就是对互斥锁上锁的线程才能将其解锁,其它线程是没有权限做这件事情的。
该函数的函数原型如下:
void unlock();
通过介绍以上三个函数,使用互斥锁进行线程同步的大致思路差不多就能搞清楚了,主要分为以下几步:
- 找到多个线程操作的共享资源(全局变量、堆内存、类成员变量等),也可以称之为临界资源