什么是死锁?
线程A 需要拿到两个mutex才能执行完毕,一个mutex1,一个mutex2,注意是有顺序的
线程B 需要拿到两个mutex才能执行完毕,一个mutex2,一个mutex1,注意是有顺序的
当线程A 拿到mutex1后,马上去拿mutex2,这时候由于线程调度。
系统调用线程B,这时候线程B 是可以拿到mutex2,但是拿不到mutex1,因为mutex1是被线程A拿着的,
于是就变成了A 拿着mutex1,然后等mutex2才能往下走。
B 拿着mutex2,然后等mutex1才能往下走。
都卡着,于是死锁发生了
死锁发生的条件
1.需要至少两个mutex。
2.需要至少两个线程。
3.两个线程需要同样的多个mutex
---这句话的意思是: A 线程 需要 mutex1,mutex2
B线程也需要mutex1,和mutex2
4.线程A 和线程B 对于mutex1,和 mutex2 的需求顺序不同。
-----这句话的意思是:A线程需要 mutex1保护一段数据,然后需要mutex2保护一段数据。
而B线程是先需要mutex2保护一段数据,然后再需要mutex1保护一段数据
死锁演示
//死锁演示 class Teacher167 { public: //共享数据 存在list中 list<int> msgListQueue; mutex mymutex1; mutex mymutex2; int readcount = 0;//记录已经读取了多少个。 public: //读取 共享数据的线程方法 void readfunc() { while (true) { //只要不为空,就可以读取数据 if (readcount >= 20000) { break; } { //先使用mymutex1 处理一段数据 mymutex1.lock();//mylock_guard在这里创建出来,在mylock_guard的构造函数中,就已经对mymutex进行了lock()操作,等到mylock_guard的生命周期结束后会析构,这时候会调用mymutex的unlock()函数 mymutex2.lock(); if (!msgListQueue.empty()) { int readvalue = msgListQueue.front();//每次都读取第一个 cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue.pop_front();//删除第一个元素 readcount++; mymutex1.unlock(); mymutex2.unlock(); } else { cout << "没有读取到值" << endl; mymutex1.unlock(); mymutex2.unlock(); } } } } //写入 共享数据的线程方法 void writefunc() { for (size_t i = 0; i < 10000; i++) { mymutex2.lock(); mymutex1.lock(); msgListQueue.push_back(i);//每次都写到末尾 cout << "写入元素的值为" << i << endl; mymutex1.unlock(); mymutex2.unlock(); } } public: Teacher167() { cout << "Teacher167 构造方法 this = " << this << endl; } Teacher167(const Teacher167 & obj) { cout << "Teacher167 copy 构造方法 this = " << this << " obj = " << &obj << endl; } ~Teacher167() { cout << "Teacher167 析构函数 this = " << this << endl; } }; void main() { cout << "=========================" << endl; Teacher167 tea1; thread readthread1(&Teacher167::readfunc, &tea1); thread writethread1(&Teacher167::writefunc, &tea1); readthread1.join(); writethread1.join(); }
死锁演示2
访问不同的数据也是一样的
//死锁演示2 class Teacher168 { public: //共享数据 存在list中 list<int> msgListQueue; list<int> msgListQueue2; mutex mymutex1; mutex mymutex2; int readcount = 0;//记录已经读取了多少个。 public: //读取 共享数据的线程方法 void readfunc() { while (true) { //只要不为空,就可以读取数据 if (readcount >= 20000) { break; } //先使用mymutex1 处理一段数据 mymutex1.lock();//mylock_guard在这里创建出来,在mylock_guard的构造函数中,就已经对mymutex进行了lock()操作,等到mylock_guard的生命周期结束后会析构,这时候会调用mymutex的unlock()函数 if (!msgListQueue.empty()) { int readvalue = msgListQueue.front();//每次都读取第一个 cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue.pop_front();//删除第一个元素 readcount++; } else { cout << "没有读取到值" << endl; } mymutex2.lock(); if (!msgListQueue2.empty()) { int readvalue = msgListQueue2.front();//每次都读取第一个 cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue2.pop_front();//删除第一个元素 readcount++; } else { cout << "222没有读取到值" << endl; } mymutex1.unlock(); mymutex2.unlock(); } } //写入 共享数据的线程方法 void writefunc() { for (size_t i = 0; i < 10000; i++) { mymutex2.lock(); mymutex1.lock(); msgListQueue.push_back(i);//每次都写到末尾 cout << "写入元素的值为" << i << endl; mymutex1.unlock(); mymutex2.unlock(); } } public: Teacher168() { cout << "Teacher168 构造方法 this = " << this << endl; } Teacher168(const Teacher168 & obj) { cout << "Teacher168 copy 构造方法 this = " << this << " obj = " << &obj << endl; } ~Teacher168() { cout << "Teacher168 析构函数 this = " << this << endl; } }; void main() { cout << "=========================" << endl; Teacher168 tea1; thread readthread1(&Teacher168::readfunc, &tea1); thread writethread1(&Teacher168::writefunc, &tea1); thread readthread2(&Teacher168::readfunc, &tea1); thread writethread2(&Teacher168::writefunc, &tea1); readthread1.join(); writethread1.join(); readthread1.join(); writethread1.join(); }
fix 死锁问题
方案一:只要保证着两个互斥量的顺序一致,就不会死锁。使用lock_guard也是一样的。
class Teacher168 { public: //共享数据 存在list中 list<int> msgListQueue; list<int> msgListQueue2; mutex mymutex1; mutex mymutex2; int readcount = 0;//记录已经读取了多少个。 public: //读取 共享数据的线程方法 void readfunc() { while (true) { //只要不为空,就可以读取数据 if (readcount >= 20000) { break; } //先使用mymutex1 处理一段数据 mymutex1.lock();//mylock_guard在这里创建出来,在mylock_guard的构造函数中,就已经对mymutex进行了lock()操作,等到mylock_guard的生命周期结束后会析构,这时候会调用mymutex的unlock()函数 if (!msgListQueue.empty()) { int readvalue = msgListQueue.front();//每次都读取第一个 cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue.pop_front();//删除第一个元素 readcount++; } else { cout << "没有读取到值" << endl; } mymutex2.lock(); if (!msgListQueue2.empty()) { int readvalue = msgListQueue2.front();//每次都读取第一个 cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue2.pop_front();//删除第一个元素 readcount++; } else { cout << "222没有读取到值" << endl; } mymutex1.unlock(); mymutex2.unlock(); } } //写入 共享数据的线程方法 void writefunc() { for (size_t i = 0; i < 10000; i++) { //fix方案,让 mymutex1.lock(),和 mymutex2.lock() 的顺序和前面的一样。 mymutex1.lock(); mymutex2.lock(); msgListQueue.push_back(i);//每次都写到末尾 cout << "写入元素的值为" << i << endl; mymutex1.unlock(); mymutex2.unlock(); } } public: Teacher168() { cout << "Teacher168 构造方法 this = " << this << endl; } Teacher168(const Teacher168 & obj) { cout << "Teacher168 copy 构造方法 this = " << this << " obj = " << &obj << endl; } ~Teacher168() { cout << "Teacher168 析构函数 this = " << this << endl; } }; void main() { cout << "=========================" << endl; Teacher168 tea1; thread readthread1(&Teacher168::readfunc, &tea1); thread writethread1(&Teacher168::writefunc, &tea1); thread readthread2(&Teacher168::readfunc, &tea1); thread writethread2(&Teacher168::writefunc, &tea1); readthread1.join(); writethread1.join(); readthread1.join(); writethread1.join(); }
方案二:std::lock(mutex1,mutex2)函数
使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了
lock(mymutex1, mymutex2);
class Teacher169 { public: //共享数据 存在list中 list<int> msgListQueue; list<int> msgListQueue2; mutex mymutex1; mutex mymutex2; int readcount = 0;//记录已经读取了多少个。 public: //读取 共享数据的线程方法 void readfunc() { while (true) { //只要不为空,就可以读取数据 if (readcount >= 20000) { break; } //fix方案2.使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了 lock(mymutex1, mymutex2); if (!msgListQueue.empty()) { int readvalue = msgListQueue.front();//每次都读取第一个 cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue.pop_front();//删除第一个元素 readcount++; } else { cout << "没有读取到值" << endl; } if (!msgListQueue2.empty()) { int readvalue = msgListQueue2.front();//每次都读取第一个 cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue2.pop_front();//删除第一个元素 readcount++; } else { cout << "222没有读取到值" << endl; } mymutex1.unlock(); mymutex2.unlock(); } } //写入 共享数据的线程方法 void writefunc() { for (size_t i = 0; i < 10000; i++) { //fix方案2,使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了 lock(mymutex1, mymutex2); msgListQueue.push_back(i);//每次都写到末尾 cout << "写入元素的值为" << i << endl; mymutex1.unlock(); mymutex2.unlock(); } } public: Teacher169() { cout << "Teacher169 构造方法 this = " << this << endl; } Teacher169(const Teacher169 & obj) { cout << "Teacher169 copy 构造方法 this = " << this << " obj = " << &obj << endl; } ~Teacher169() { cout << "Teacher169 析构函数 this = " << this << endl; } }; void main() { cout << "=========================" << endl; Teacher169 tea1; thread readthread1(&Teacher169::readfunc, &tea1); thread writethread1(&Teacher169::writefunc, &tea1); thread readthread2(&Teacher169::readfunc, &tea1); thread writethread2(&Teacher169::writefunc, &tea1); readthread1.join(); writethread1.join(); readthread1.join(); writethread1.join(); }
方案二进阶版:使用 lock_guard<mutex> 替代 unlock
//fix方案进阶版.使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了
// 这里使用 lock_guard<mutex> sbguard1(mymutex1,adopt_lock);的含义是:在lock_guard构造函数中不调用 mutex1.lock(),只是在析构函数中调用 mutex1.unlock();
lock(mymutex1, mymutex2);
lock_guard<mutex> sbguard1(mymutex1,adopt_lock);
lock_guard<mutex> sbguard2(mymutex2, adopt_lock);
class Teacher170 { public: //共享数据 存在list中 list<int> msgListQueue; list<int> msgListQueue2; mutex mymutex1; mutex mymutex2; int readcount = 0;//记录已经读取了多少个。 public: //读取 共享数据的线程方法 void readfunc() { while (true) { //只要不为空,就可以读取数据 if (readcount >= 20000) { break; } //fix方案3.使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了 // 这里使用 lock_guard<mutex> sbguard1(mymutex1,adopt_lock);的含义是:在lock_guard构造函数中不调用 mutex1.lock(),只是在析构函数中调用 mutex1.unlock(); lock(mymutex1, mymutex2); lock_guard<mutex> sbguard1(mymutex1,adopt_lock); lock_guard<mutex> sbguard2(mymutex2, adopt_lock); if (!msgListQueue.empty()) { int readvalue = msgListQueue.front();//每次都读取第一个 cout << "读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue.pop_front();//删除第一个元素 readcount++; } else { cout << "没有读取到值" << endl; } if (!msgListQueue2.empty()) { int readvalue = msgListQueue2.front();//每次都读取第一个 cout << "222读取到的值为" << readvalue << " readcount = " << readcount << endl; msgListQueue2.pop_front();//删除第一个元素 readcount++; } else { cout << "222没有读取到值" << endl; } } } //写入 共享数据的线程方法 void writefunc() { for (size_t i = 0; i < 10000; i++) { //fix方案2,使用lock(mutex1,mutex2); 参数都是mutex,代表的就是顺序,因此保证每个地方的调用都是一样的就可以了 lock(mymutex1, mymutex2); lock_guard<mutex> sbguard1(mymutex1, adopt_lock); lock_guard<mutex> sbguard2(mymutex2, adopt_lock); msgListQueue.push_back(i);//每次都写到末尾 cout << "写入元素的值为" << i << endl; } } public: Teacher170() { cout << "Teacher170 构造方法 this = " << this << endl; } Teacher170(const Teacher170 & obj) { cout << "Teacher170 copy 构造方法 this = " << this << " obj = " << &obj << endl; } ~Teacher170() { cout << "Teacher170 析构函数 this = " << this << endl; } }; void main() { cout << "=========================" << endl; Teacher170 tea1; thread readthread1(&Teacher170::readfunc, &tea1); thread writethread1(&Teacher170::writefunc, &tea1); thread readthread2(&Teacher170::readfunc, &tea1); thread writethread2(&Teacher170::writefunc, &tea1); readthread1.join(); writethread1.join(); readthread2.join(); writethread2.join(); }