1 互斥锁Mutex

1.1 基本概念

在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。比如说,同一个文件,可能一个线程会对其进行写操作,而另一个线程需要对这个文件进行读操作,可想而知,如果写线程还没有写结束,而此时读线程开始了,或者读线程还没有读结束而写线程开始了,那么最终的结果显然会是混乱的。为了保护共享资源,在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

在多线程环境中,有多个线程竞争同一个公共资源,就很容易引发线程安全的问题。因此就需要引入锁的机制,来保证任意时候只有一个线程在访问公共资源。

  • 互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
  • 互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。

1.2 互斥锁的特点

在线程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
可以保证以下三点:

  1. 原子性:把一个互斥量锁定为一个原子操作,这意味着如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
  2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
  3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。


1.3 互斥锁的使用

互斥锁主要就是用来保护共享资源的,在C++ 11中,互斥锁封装在mutex类中,通过调用类成员函数lock()和unlock()来实现加锁和解锁。值得注意的是,加锁和解锁,必须成对使用。

包含头文件#include

1.3.1 lock()和unlock()

  1. 说明:lock确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。unlock解除共享锁。
  2. 声明:​​void lock(); void unlock();​
  3. 使用举例:
mutex g_mutex;
g_mutex.lock();
....
g_mutex.unlock();

1.3.2 try_lock_for()

  1. 说明:尝试上锁,如果前面已经上锁了 则返回false,否则返回true。
  2. 声明:
bool try_lock();

1.3.3 使用

步骤:1.lock(); 2.操作共享数据;3.unlock()。
实例1:

未加锁:
#include <iostream>
#include <thread>
using namespace std;
void print_thread_id(int id) {
cout << "thread " << id << '\n';
}
int main() {
thread threads[10];
for (int i = 0; i < 10; i++) {
threads[i] = thread(print_thread_id, i + 1);
}
for (auto& th : threads)
th.join();
return 0;
}

加锁:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;
void print_thread_id(int id) {
mtx.lock();
cout << "thread " << id << '\n';
mtx.unlock();
}
int main() {
thread threads[10];
for (int i = 0; i < 10; i++) {
threads[i] = thread(print_thread_id, i + 1);
}
for (auto& th : threads)
th.join();
return 0;
}

未加锁:
【Window】互斥锁——Mutex,lock_guard,unique_lock_#include
加锁:
【Window】互斥锁——Mutex,lock_guard,unique_lock_#include_02
由于锁是互斥的,一个线程在执行期间是不允许其他线程进行加锁,其他线程就会被阻塞,直到加锁的线程完成打印任务开锁。

lock()和unlock()要成对使用,不能重复上锁和解锁。本质就是lock~unlock之间的程序(数据)不会同时调用、修改。

实例2:
下面再举一个更为直观的例子,创建两个线程同时对list进行写操作, 这里用两个线程来写list,并且最终在主线程中调用了showList()来输出list的size和所有元素,我们先来看下输出情况:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class msgList
{
private:
list<int> mylist;
mutex m;
int i = 0;
public:
void WriteList()
{
while (i < 1000)
{
m.lock();
mylist.push_back(i++);
m.unlock();
}
return;
}
void showList()
{
for (auto p = mylist.begin(); p != mylist.end(); p++)
{
cout << (*p) << " ";
}
cout << endl;
cout << "size of list : " << mylist.size() << endl;
return;
}
};
int main()
{
msgList mlist;
thread pwrite0(&msgList::WriteList, &mlist);
thread pwrite1(&msgList::WriteList, &mlist);

pwrite0.join();
pwrite1.join();
cout << "threads end!" << endl;

mlist.showList(); //子线程结束后主线程打印list
return 0;
}

【Window】互斥锁——Mutex,lock_guard,unique_lock_#include_03

数字都是连续的,但是个数却多了一个(出现的几率还是比较小),这又是什么原因造成的呢?还是两个线程的问题,假设要插入1000个数,循环条件就是while(i<1000),当i=999的时候两个写线程都可以进入while循环,此时如果pwrite0线程拿到了lock(),那么pwrite1线程就只能一直等待,pwrite0线程继续往下执行,使得i变成了1000,此时,对于pwrite0线程来说,它就必须退出循环了。而此时的pwrite1在哪里呢?还等在lock()的地方,pwrite0线程unlock()后,pwrite1成功lock(),此时i=1000,但是pwrite1却还没有执行完此次循环,因此向list中插入1000,此时退出的i的值为1001,这也就造成了实际输出为1001个数的情况。

为了避免这个问题,一个简单的办法就是在lock()之后再加上一个判断,判断i是否依旧满足while的条件,如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class msgList
{
private:
list<int> mylist;
mutex m;
int i = 0;
public:
void WriteList()
{
while (i < 1000)
{
m.lock();
if (i >= 1000)
{
m.unlock(); //退出之前必须先解锁
break;
}
mylist.push_back(i++);
m.unlock();
}
return;
}
void showList()
{
for (auto p = mylist.begin(); p != mylist.end(); p++)
{
cout << (*p) << " ";
}
cout << endl;
cout << "size of list : " << mylist.size() << endl;
return;
}
};
int main()
{
msgList mlist;
thread pwrite0(&msgList::WriteList, &mlist);
thread pwrite1(&msgList::WriteList, &mlist);

pwrite0.join();
pwrite1.join();
cout << "threads end!" << endl;

mlist.showList(); //子线程结束后主线程打印list
return 0;
}

【Window】互斥锁——Mutex,lock_guard,unique_lock_#include_04

为什么这里要在break前面加一个unlock()呢?原因就在于:如果break前面没有unlock(),一旦i符合了if的条件,就直接break了,此时就没法unlock(),程序就会报错。【Window】互斥锁——Mutex,lock_guard,unique_lock_加锁_05

这种错误是比较难发现的,特别是像这样程序中出现了分支的情况,很容易就使得程序实际运行时lock()了却没有unclock()。为了解决这一问题,就有了std::lock_guard。

2 Mutex的优秀辅助

2.1 std::lock_guard

作用:此类辅助锁定互斥量,构造时锁定,析构时解锁,避免遗忘解锁,也就是说在其作用域内他会一直锁定要求的变量。即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁

简单来理解的话,lock_guard就是一个类,它会在其构造函数中加锁,而在析构函数中解锁,也就是说,只要创建一个lock_guard的对象,就相当于lock()了,而该对象析构时,就自动调用unlock()了。

模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型。
标准库中定义几种基本的 BasicLockable 类型,分别 :

  • std::mutex,
  • std::recursive_mutex,
  • std::timed_mutex,
  • std::recursive_timed_mutex ;


📌📌注:

  • BasicLockable 类型的对象只需满足两种操作: lock 和 unlock;
  • 另外还有 Lockable 类型,在 BasicLockable 类型的基础上新增了 try_lock 操作,因此一个满足 Lockable 的对象应支持三种操作:lock,unlock 和 try_lock;
  • 最后还有一种 TimedLockable 对象,在 Lockable 类型的基础上又新增了 try_lock_for 和 try_lock_until 两种操作,因此一个满足 TimedLockable
    的对象应支持五种操作:lock, unlock, try_lock, try_lock_for, try_lock_until)。


优点: 在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。
缺点: lock_guard 最大的缺点也是简单,没有给程序员提供足够的灵活度。

2.1.1 构造函数

  1. lock_guard 构造函数如下表所示:
locking 初始化: lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock}
adopting 初始化:lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) // construct but don't lock}
拷贝构造: lock_guard(const lock_guard&) = delete;
  1. 比较说明
    locking 初始化: lock_guard 对象管理 Mutex 对象 m,并在构造时对 m 进行上锁(调用m.lock())。
    adopting初始化: lock_guard 对象管理 Mutex 对象 m,与 locking初始化不同的是, Mutex对象m已被当前线程锁住。
    拷贝构造: lock_guard 对象的拷贝构造和移动构造(move construction)均被禁用,因此 lock_guard 对象不可被拷贝构造或移动构造。

2.1.2 应用举例

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class msgList
{
private:
list<int> mylist;
mutex m;
int i = 0;
public:
void WriteList()
{
while (i < 1000)
{
lock_guard<mutex> guard(m); //创建lock_guard的类对象guard,用互斥量m来构造
if (i >= 1000)
{
break;
}
mylist.push_back(i++);
}
return;
}
void showList()
{
for (auto p = mylist.begin(); p != mylist.end(); p++)
{
cout << (*p) << " ";
}
cout << endl;
cout << "size of list : " << mylist.size() << endl;
return;
}
};
int main()
{
msgList mlist;
thread pwrite0(&msgList::WriteList, &mlist);
thread pwrite1(&msgList::WriteList, &mlist);

pwrite0.join();
pwrite1.join();
cout << "threads end!" << endl;

mlist.showList(); //子线程结束后主线程打印list
return 0;
}

这里主要有两个需要注意的地方:

  • 原先的lock()和unlock()都不用了;
  • if中的break前面也不用再调用unlock()了。


这都是因为对象guard在lock_guard构造出来,同时就调用了lock(),当退出while时,guard析构,析构时就调用了unlock()。(局部对象的生命周期就是创建该对象时离其最近的大括号的范围{})

2.2 std::unique_lock函数模板

unique_lock 对象以独占所有权的方式( unique owership)管理 mutex 对象的上锁和解锁操作,所谓独占所有权,就是没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权。

std::unique_lock 对象也能保证在其自身析构时它所管理的 Mutex 对象能够被正确地解锁(即使没有显式地调用 unlock 函数)。因此,和 lock_guard 一样,这也是一种简单而又安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。

unique_lock是具有析构函数的高级类,可以解锁互斥锁。unique_lock想比于lock_guard,都是基于RAII思想的,也支持std::lock_guard的功能,但是区别在于它提供更多的成员函数,比如:lock(),unlock()使用更加灵活,并且可以和condiction_variable一起使用控制线程同步。但是效率差一点,内存占用多一点。提供了更好的上锁和解锁控制。

2.2.1 unique_lock构造

  1. 默认构造函数
unique_lock()

新创建的 unique_lock 对象,不管理任何 Mutex 对象。

  1. locking 初始化
explicit unique_lock(_Mutex& _Mtx)

新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞。

  1. adopting 初始化
unique_lock(_Mutex& _Mtx, adopt_lock_t)

新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。

  1. deferred 初始化
unique_lock(_Mutex& _Mtx, defer_lock_t)

新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象。

加上defer_lock是始化了一个没有加锁的mutex,不给它加锁的目的是以后可以调用后面提到的unique_lock的一些方法

  1. try-locking 初始化
unique_lock(_Mutex& _Mtx, try_to_lock_t)

新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。但也不能操作保护的数据(防止异常),只能操作不受保护的数据;

使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方

  1. locking 一段时间(duration):
template <class _Rep, class _Period>
unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time)

新创建的 unique_lock 对象管理 Mutex 对象 m,并试图通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。

  1. locking 直到某个时间点(time point):
template <class _Clock, class _Duration>
unique_lock(_Mutex& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time)

新创建的 unique_lock 对象管理 Mutex 对象m,并试图通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。

  1. 移动(move)构造
unique_lock(unique_lock&& _Other)

新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。

  1. 拷贝构造 [被禁用]
unique_lock(const unique_lock&) = delete;
unique_lock& operator=(const unique_lock&) = delete;

unique_lock 对象不能被拷贝构造。


综上所述,由 2 和 3 创建的 unique_lock 对象通常拥有 Mutex 对象的锁。而通过1 和4 创建的则不会拥有锁。通过
5,6 和7 创建的 unique_lock 对象,则在 lock 成功时获得锁。

2.2.2 unique_lock的成员函数

1)lock()

  1. 声明
    上锁操作,调用它所管理的 Mutex 对象的 lock 函数。如果在调用 Mutex 对象的 lock 函数时该 Mutex对象已被另一线程锁住,则当前线程会被阻塞,直到它获得了锁。
    该函数返回时,当前的 unique_lock 对象便拥有了它所管理的 Mutex 对象的锁。如果上锁操作失败,则抛出
    system_error 异常。
void lock()
  1. 使用
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();

2)try_lock()

  1. 说明
    作用:尝试给互斥量加锁,如果拿不到锁,返回false,否则返回true。
bool try_lock()

3)try_lock_for()

  1. 说明
    作用:尝试上锁,如果前面已经上锁了, 则返回false,否则返回true。
bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time)
  1. 使用
auto _500ms = std::chrono::milliseconds(0);
if (mutex.try_lock_for(_500ms)) // 如果前面已经上锁了 那就返回false 否则返回true
{
std::cout << "获得了锁" << std::endl;
}else{
std::cout << "未获得锁" << std::endl;
}

4)try_lock_until

上锁操作,调用它所管理的 Mutex 对象的 try_lock_for 函数,如果上锁成功,则返回 true,否则返回 false。

bool try_lock_until(const chrono::time_point<_Clock, _Duration>& _Abs_time)
bool try_lock_until(const xtime* _Abs_time)

5) unlock():解锁

  1. 声明
    作用:因为一些非共享代码要处理,可以暂时先unlock(),用其他线程把它们处理了,处理完后再lock()。
void unlock()
  1. 使用
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
...//处理一些共享代码
myUniLock.unlock();
...//处理一些非共享代码
myUniLock.lock();
...//处理一些共享代码

6)release()

  1. 说明
    作用:释放unique_lock所管理的mutex对象指针。类似于转移所有权。

myUniLock(myMutex)相当于把myMutex和myUniLock绑定在了一起,release()就是解除绑定,返回它所管理的mutex对象的指针,并释放所有权。

_Mutex* release()
  1. 使用
mutex* ptx =myUniLock.release();

所有权由ptx接管,如果原来mutex对象处于加锁状态,就需要自己进行解锁了ptx->unlock();。

7)owns_lock()

  1. 说明
    判断是否拿到锁,如拿到返回true
bool owns_lock()
  1. 使用
std::unique_lock<std::mutex> my_unique(my_mutex, std::try_to_lock);
if(my_unique.owns_lock()){
...//处理一些共享代码
}else{
cout<<"没拿到锁"<<endl;
}

8)std::operator bool()

  1. 说明
    与 owns_lock 功能相同,返回当前 std::unique_lock 对象是否获得了锁。
operator bool()
  1. 使用
#include <iostream>  
#include <vector>
#include <thread>
#include <mutex>

std::mutex mtx; // mutex for critical section
void print_star () {
std::unique_lock<std::mutex> lck(mtx,std::try_to_lock);
if (lck)
std::cout << '*';
else
std::cout << 'x';
}
int main ()
{
std::vector<std::thread> threads;
for (int i=0; i<500; ++i)
threads.emplace_back(print_star);
for (auto& x: threads) x.join();
return 0;
}

2.2.3 应用和举例

举例:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class msgList
{
private:
list<int> mylist;
mutex m;
int i = 0;
public:
void WriteList()
{
while (i < 1000)
{
unique_lock<std::mutex> my_guard(m);
if (i >= 1000)
{
break;
}
mylist.push_back(i++);
}
return;
}
void showList()
{
for (auto p = mylist.begin(); p != mylist.end(); p++)
{
cout << (*p) << " ";
}
cout << endl;
cout << "size of list : " << mylist.size() << endl;
return;
}
};
int main()
{
msgList mlist;
thread pwrite0(&msgList::WriteList, &mlist);
thread pwrite1(&msgList::WriteList, &mlist);

pwrite0.join();
pwrite1.join();
cout << "threads end!" << endl;

mlist.showList(); //子线程结束后主线程打印list
return 0;
}

3 死锁

std::lock(mutex1,mutex2……):一次锁定多个互斥量(一般这种情况很少),用于处理多个互斥量。

如果有一个没锁住,就会把已经锁住的释放掉,然后它就等待,等所有互斥量都可以同时锁住,才继续执行。(要么互斥量都锁住,要么都没锁住,防止死锁)

3.1 死锁的含义

死锁是什么意思呢?

举个例子:我和你手里都拽着对方家门的钥匙,我说:“你不把我的锁还来,我就不把你的锁给你!”,你一听不乐意了,也说:“你不把我的锁还来,我也不把你的锁给你!”。就这样,我们两个人互相拿着对方的锁又等着对方先把锁拿来,然后就只能一直等着等着等着…最终谁也拿不到自己的锁,这就是死锁。

显然,死锁是发生在至少两个锁之间的,也就是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行,当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

3.2 发生原因

死锁至少有两个互斥量mutex1,mutex2。

  • 线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
  • 线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1。

此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。

3.3 死锁的例子

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
mutex mutex1, mutex2;
int i = 0;
void Thread1()
{
while (i < 100)
{
lock_guard<mutex> myLG1(mutex1); //线程1加锁1
lock_guard<mutex> myLG2(mutex2); //线程1加锁2
cout << "thread 1 running..." << i++ << endl;
}
return;
}
void Thread2()
{
while (i < 100)
{
lock_guard<mutex> myLG1(mutex2); //线程2加锁2
lock_guard<mutex> myLG2(mutex1); //线程2加锁1
cout << "thread 2 running... " << i++ << endl;
}
return;
}
int main()
{
thread p0(Thread1);
thread p1(Thread2);
p0.join();
p1.join();
return 0;
}

【Window】互斥锁——Mutex,lock_guard,unique_lock_#include_06
这就出现了死锁。产生的原因就是因为在线程1中,先加锁1,再加锁2;在线程2中,先加锁2,再加锁1;如果两个线程之一能够完整执行的话,那自然是没有问题的。但是如果某个时刻,线程1中刚加锁1,就上下文切换到线程2,此时线程2就加锁2,然后此时两个线程都想向下执行的话,线程2就必须等待线程1解锁1,线程1就必须等待线程2解锁2,就这样两个线程都一直阻塞着,形成了死锁。

3.4 解决办法

3.4.1 按顺序加锁

只要保证多个互斥量上锁的顺序一样就不会造成死锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
mutex mutex1, mutex2;
int i = 0;
void Thread1()
{
while (i < 100)
{
lock_guard<mutex> myLG1(mutex1); //线程1加锁1
lock_guard<mutex> myLG2(mutex2); //线程1加锁2
cout << "thread 1 running..." << i++ << endl;
}
return;
}
void Thread2()
{
while (i < 100)
{
lock_guard<mutex> myLG1(mutex1); //线程2加锁1
lock_guard<mutex> myLG2(mutex2); //线程2加锁2
cout << "thread 2 running..." << i++ << endl;
}
return;
}
int main()
{
thread p0(Thread1);
thread p1(Thread2);
p0.join();
p1.join();
return 0;
}

【Window】互斥锁——Mutex,lock_guard,unique_lock_加锁_07
在这种情况下,两个线程一旦一个加了锁,那么另一个就必定阻塞,这样,就不会出现两边加锁两边阻塞的情况,从而避免死锁。

3.4.2 同时上锁

同时上锁需要用到lock()函数:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
mutex mutex1, mutex2;
int i = 0;
void Thread1()
{
while (i < 100)
{
lock(mutex1, mutex2);
lock_guard<mutex> myLG1(mutex1,adopt_lock); //线程1加锁1
lock_guard<mutex> myLG2(mutex2,adopt_lock); //线程1加锁2
cout << "thread 1 running..." << i++ << endl;
}
return;
}
void Thread2()
{
while (i < 100)
{
lock(mutex1, mutex2);
lock_guard<mutex> myLG1(mutex1, adopt_lock);
lock_guard<mutex> myLG2(mutex2, adopt_lock);
cout << "thread 2 running..." << i++ << endl;
}
return;
}
int main()
{
thread p0(Thread1);
thread p1(Thread2);
p0.join();
p1.join();
return 0;
}

注意到这里的lock_guard中多了第二个参数adopt_lock,这个参数表示在调用lock_guard时,已经加锁了,防止lock_guard在对象生成时构造函数再次lock()。