
C++ 并发与多线程(五)
发布日期:2021-05-07 15:56:03
浏览次数:8
分类:原创文章
本文共 46814 字,大约阅读时间需要 156 分钟。
unique_lock详解
1.unique_lock取代lock_guard
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); std::unique_lock<std::mutex>sbguard1(my_mutex1); msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。}
2.unique_lock的第二个参数
2.1 adopt_lock
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); my_mutex1.lock(); //in线程后到 std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::chrono::milliseconds dura(20000); //等待20s std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 std::unique_lock<std::mutex>sbguard1(my_mutex1); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //使用}
2.2 try_to_lock
尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞,用try_to_ lock的前提是不可以提前上锁,否则异常。
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 if (sbguard1.owns_lock()) { cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 } else { cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; } //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(10); //等待10ms std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try to lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try to lock的前提是不可以手动上锁,否则异常}
当没有上锁成功时,try to lock可以通过判断条件进入自定义事件
2.3 std::defer_lock
初始化一个没有上锁的互斥量mutex
3.unique_lock的成员函数
3.1 lock() 加锁
通过调用unique_lock的成员函数lock,自动上锁和解锁,配合defer_lock使用。
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 //if (sbguard1.owns_lock()) //如果尝试上锁成功 //{ cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; //创建没有加锁的my_mutex1 std::unique_lock<std::mutex>sbguard1(my_mutex1, std::defer_lock); sbguard1.lock(); //不需要手动解锁 msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 //} //else // 如果上锁失败 //{ // cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; //} //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(100); //等待20s std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try to lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try to lock的前提是不可以手动上锁,否则异常 //7.3 std::defer_lock //使用defer_lock的前提是 不能自己先lock,否则异常,与try to lock一样,与adopt_lock不同 //defer_lock 初始化一个没有加锁的mutex //借着defer_lock的话题,介绍一些unique_lock的重要成员函数 //八 unique_lcok的成员函数 // 8.1 lock()}
3.2 unlock()
解锁:前提是已经加锁
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 //if (sbguard1.owns_lock()) //如果尝试上锁成功 //{ cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; //创建没有加锁的my_mutex1 std::unique_lock<std::mutex>sbguard1(my_mutex1, std::defer_lock); sbguard1.lock(); //不需要手动解锁 //处理共享代码 sbguard1.unlock(); //不需要手动解锁 //处理非共享代码 sbguard1.lock(); //不需要手动解锁 //处理共享代码 msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 //} //else // 如果上锁失败 //{ // cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; //} //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(100); //等待20s std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try to lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try to lock的前提是不可以手动上锁,否则异常 //7.3 std::defer_lock //使用defer_lock的前提是 不能自己先lock,否则异常,与try to lock一样,与adopt_lock不同 //defer_lock 初始化一个没有加锁的mutex //借着defer_lock的话题,介绍一些unique_lock的重要成员函数 //八 unique_lcok的成员函数 // 8.1 lock() //8.2 unlock() 希望处理些非共享代码,处理非共享代码,然后再lock上 //即unique_lock通过成员函数可以实现手动上锁,自动解锁和手动解锁}
程序稳定运行
3.3 try_lock
与defer_lock配合使用
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 //if (sbguard1.owns_lock()) //如果尝试上锁成功 //{ //创建没有加锁的my_mutex1 std::unique_lock<std::mutex>sbguard1(my_mutex1, std::defer_lock); //sbguard1.lock(); //不需要手动解锁 //处理共享代码 //sbguard1.unlock(); //不需要手动解锁 //处理非共享代码 //sbguard1.lock(); //不需要手动解锁 //处理共享代码 if (sbguard1.try_lock() == true) { cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 } else { cout << "inMsgRecvQueue执行,上锁失败" << endl; } //} //else // 如果上锁失败 //{ // cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; //} //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(200); //等待200ms std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try_to_lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try_to_lock的前提是不可以手动上锁,否则异常 //7.3 std::defer_lock //使用defer_lock的前提是 不能自己先lock,否则异常,与try to lock一样,与adopt_lock不同 //defer_lock 初始化一个没有加锁的mutex //借着defer_lock的话题,介绍一些unique_lock的重要成员函数 //八 unique_lcok的成员函数 // 8.1 lock() //8.2 unlock() 希望处理些非共享代码,处理非共享代码,然后再lock上 //即unique_lock通过成员函数可以实现手动上锁,自动解锁和手动解锁 //也可以自己在程序结尾自己家unlock unique_lock会作出判断 //8.3 try_lock 尝试给互斥量加锁,如果拿不到锁,则返回false,拿到了则返回true,程序不阻塞}
3.4 release()
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 //if (sbguard1.owns_lock()) //如果尝试上锁成功 //{ //创建没有加锁的my_mutex1 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::defer_lock); //sbguard1.lock(); //不需要手动解锁 //处理共享代码 //sbguard1.unlock(); //不需要手动解锁 //处理非共享代码 //sbguard1.lock(); //不需要手动解锁 //处理共享代码 std::unique_lock<std::mutex>sbguard1(my_mutex1); // 创建一个加锁的mutex std::mutex *ptx = sbguard1.release(); //将sbguard1与my_mutex1解绑 手动解锁 //if (sbguard1.try_lock() == true) //{ cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 ptx->unlock(); //手动解锁 //} //else //{ // cout << "inMsgRecvQueue执行,上锁失败" << endl; // //} //} //else // 如果上锁失败 //{ // cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; //} //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(10); //等待10ms std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try_to_lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try_to_lock的前提是不可以手动上锁,否则异常 //7.3 std::defer_lock //使用defer_lock的前提是 不能自己先lock,否则异常,与try to lock一样,与adopt_lock不同 //defer_lock 初始化一个没有加锁的mutex //借着defer_lock的话题,介绍一些unique_lock的重要成员函数 //八 unique_lcok的成员函数 // 8.1 lock() //8.2 unlock() 希望处理些非共享代码,处理非共享代码,然后再lock上 //即unique_lock通过成员函数可以实现手动上锁,自动解锁和手动解锁 //也可以自己在程序结尾自己家unlock unique_lock会作出判断 //8.3 try_lock 尝试给互斥量加锁,如果拿不到锁,则返回false,拿到了则返回true,程序不阻塞 //8.4 release(),返回它管理的mutex对象指针,并释放所有权,也就是说,这个unique_lock和mutex不再有关系 //严格区分unlock()和release()的区别,不要混淆 //如果原来mutex对象处于加锁状态,如果release了,则需要手动解锁 //release返回原始的mutex指针 //为什么有时候需要unlock():因为lock锁住的代码段越少,执行越快,整个程序运行效率越高 //乘互斥量锁住的代码多少,称为粒度 ,粒度一般用粗细描述 //要选择合适粒度的代码来保护共享数据,粒度太细影响效率,粒度太粗影响效率 }
4.unique_lock所有权的传递
一个mutex只和一个mutex绑定在一起
所有权概念: sbguard1拥有my_mutex1的所有权,sbguard1可以把对my_mutex1的所有权转一个其他unique_lock对象,
所以,unique_lock的所有权,可以转移,不能复制。
std::unique_lockstd::mutexsbguard1(my_mutex1);
std::unique_lockstd::mutexsbguard2(sbguard1); 这样是非法的
std::unique_lockstd::mutexsbguard2(std::move(sbguard1)); //将sbguard1的所有权转移给sbguard2
4.1 调用std::move转移所有权
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 //if (sbguard1.owns_lock()) //如果尝试上锁成功 //{ //创建没有加锁的my_mutex1 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::defer_lock); //sbguard1.lock(); //不需要手动解锁 //处理共享代码 //sbguard1.unlock(); //不需要手动解锁 //处理非共享代码 //sbguard1.lock(); //不需要手动解锁 //处理共享代码 std::unique_lock<std::mutex>sbguard1(my_mutex1); // 创建一个加锁的mutex std::unique_lock<std::mutex>sbguard2(std::move(sbguard1)); //将sbguard1的所有权转移给sbguard2 //现在sbguard1指向空 sbguard2指向了my_mutex1 /*std::mutex *ptx = sbguard1.release(); */ //将sbguard1与my_mutex1解绑 手动解锁 //if (sbguard1.try_lock() == true) //{ cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 sbguard2.unlock(); //手动解锁 //} //else //{ // cout << "inMsgRecvQueue执行,上锁失败" << endl; // //} //} //else // 如果上锁失败 //{ // cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; //} //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(10); //等待10ms std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try_to_lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try_to_lock的前提是不可以手动上锁,否则异常 //7.3 std::defer_lock //使用defer_lock的前提是 不能自己先lock,否则异常,与try to lock一样,与adopt_lock不同 //defer_lock 初始化一个没有加锁的mutex //借着defer_lock的话题,介绍一些unique_lock的重要成员函数 //八 unique_lcok的成员函数 // 8.1 lock() //8.2 unlock() 希望处理些非共享代码,处理非共享代码,然后再lock上 //即unique_lock通过成员函数可以实现手动上锁,自动解锁和手动解锁 //也可以自己在程序结尾自己家unlock unique_lock会作出判断 //8.3 try_lock 尝试给互斥量加锁,如果拿不到锁,则返回false,拿到了则返回true,程序不阻塞 //8.4 release(),返回它管理的mutex对象指针,并释放所有权,也就是说,这个unique_lock和mutex不再有关系 //严格区分unlock()和release()的区别,不要混淆 //如果原来mutex对象处于加锁状态,如果release了,则需要手动解锁 //release返回原始的mutex指针 //为什么有时候需要unlock():因为lock锁住的代码段越少,执行越快,整个程序运行效率越高 //乘互斥量锁住的代码多少,称为粒度 ,粒度一般用粗细描述 //要选择合适粒度的代码来保护共享数据,粒度太细影响效率,粒度太粗影响效率 // 九 unique_lock的所有权传递 // 一个mutex只和一个mutex绑定在一起 //所有权概念: sbguard1拥有my_mutex1的所有权,sbguard1可以把对my_mutex1的所有权转一个其他unique_lock对象, //所以,unique_lock的所有权,可以转移,不能复制。 //std::unique_lock<std::mutex>sbguard1(my_mutex1); //std::unique_lock<std::mutex>sbguard2(sbguard1); 这样是非法的 //std::unique_lock<std::mutex>sbguard2(std::move(sbguard1)); //将sbguard1的所有权转移给sbguard2}
4.2 使用A的类成员函数返回unique_lock对象
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 std::unique_lock<std::mutex>rtn_unique_lock() { std::unique_lock<std::mutex>temguard(my_mutex1); return temguard; //从函数返回一个局部的unique_lock对象是可以的,移动构造函数 //返回这种局部对象temguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数 } void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() //std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock //std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); //std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); //std::unique_lock<std::mutex>sbguard1(my_mutex1); //my_mutex1.lock(); //in线程后到 //std::unique_lock<std::mutex>sbguard1(my_mutex1,adopt_lock); //使用了adopt_lock 需要提前lock //尝试加锁 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::try_to_lock); //使用此参数的条件时互斥量没有上锁 //if (sbguard1.owns_lock()) //如果尝试上锁成功 //{ //创建没有加锁的my_mutex1 //std::unique_lock<std::mutex>sbguard1(my_mutex1, std::defer_lock); //sbguard1.lock(); //不需要手动解锁 //处理共享代码 //sbguard1.unlock(); //不需要手动解锁 //处理非共享代码 //sbguard1.lock(); //不需要手动解锁 //处理共享代码 //std::unique_lock<std::mutex>sbguard1(my_mutex1); // 创建一个加锁的mutex //std::unique_lock<std::mutex>sbguard2(std::move(sbguard1)); //将sbguard1的所有权转移给sbguard2 //现在sbguard1指向空 sbguard2指向了my_mutex1 /*std::mutex *ptx = sbguard1.release(); */ //将sbguard1与my_mutex1解绑 手动解锁 //if (sbguard1.try_lock() == true) //{ std::unique_lock<std::mutex>sbguard1 = rtn_unique_lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 sbguard1.unlock(); //手动解锁 //} //else //{ // cout << "inMsgRecvQueue执行,上锁失败" << endl; // //} //} //else // 如果上锁失败 //{ // cout << "inMsgRecvQueue 执行,但没有上锁。。。。" << endl; //} //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); //std::lock(my_mutex1, my_mutex2); std::unique_lock<std::mutex>sbguard1(my_mutex1); std::chrono::milliseconds dura(10); //等待10ms std::this_thread::sleep_for(dura); //休息一定时长 outMsgLULproc先跑起来 if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex1.unlock(); my_mutex2.unlock();*/ return true; } else { //my_mutex1.unlock(); //my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数 //六 unique_lock取代lock_guard //unique_lock是个类模板,工作中推荐使用lock_guard //lock_guard取代了mutex的lock和unlock //unique_lock比lock_guard灵活,效率略差,内存占用稍多。 //七 unique_lock的第二个参数 // 7.1 adopt_lock //lock_guard中的adopt_lock起标记作用 表示互斥量已经被lock了,在写这个标记前需要把互斥量lock 否则报错 //adopt的效果是:假设调用方 线程已经拥有了互斥的所有权(已经lock成功了) //unique_lock也有adopt_lock标记,含义相同。 //灵活性: //对于同一个锁 如果其中一个线程上了锁,则另一个也需要这个锁的线程就会处于等待状态,知道前一个线程解锁,会浪费很多时间 //7.2 std::try_to_lock //尝试用mutex的lock()去锁定lock,但如果没有锁定成功,也会立即返回,并不会阻塞 //用try_to_lock的前提是不可以手动上锁,否则异常 //7.3 std::defer_lock //使用defer_lock的前提是 不能自己先lock,否则异常,与try to lock一样,与adopt_lock不同 //defer_lock 初始化一个没有加锁的mutex //借着defer_lock的话题,介绍一些unique_lock的重要成员函数 //八 unique_lcok的成员函数 // 8.1 lock() //8.2 unlock() 希望处理些非共享代码,处理非共享代码,然后再lock上 //即unique_lock通过成员函数可以实现手动上锁,自动解锁和手动解锁 //也可以自己在程序结尾自己家unlock unique_lock会作出判断 //8.3 try_lock 尝试给互斥量加锁,如果拿不到锁,则返回false,拿到了则返回true,程序不阻塞 //8.4 release(),返回它管理的mutex对象指针,并释放所有权,也就是说,这个unique_lock和mutex不再有关系 //严格区分unlock()和release()的区别,不要混淆 //如果原来mutex对象处于加锁状态,如果release了,则需要手动解锁 //release返回原始的mutex指针 //为什么有时候需要unlock():因为lock锁住的代码段越少,执行越快,整个程序运行效率越高 //乘互斥量锁住的代码多少,称为粒度 ,粒度一般用粗细描述 //要选择合适粒度的代码来保护共享数据,粒度太细影响效率,粒度太粗影响效率 // 九 unique_lock的所有权传递 // 一个mutex只和一个mutex绑定在一起 //所有权概念: sbguard1拥有my_mutex1的所有权,sbguard1可以把对my_mutex1的所有权转一个其他unique_lock对象, //所以,unique_lock的所有权,可以转移,不能复制。 //std::unique_lock<std::mutex>sbguard1(my_mutex1); //std::unique_lock<std::mutex>sbguard2(sbguard1); 这样是非法的 //std::unique_lock<std::mutex>sbguard2(std::move(sbguard1)); //将sbguard1的所有权转移给sbguard2}
可以看到my_mutex1的地址被绑定到了sbguard1上了
发表评论
最新留言
做的很好,不错不错
[***.243.131.199]2025年03月30日 16时28分09秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
03-selenium元素定位
2019-03-04
19-selenium操作已启动的浏览器
2019-03-04
2020.2.13普及C组 罗密欧与朱丽叶的约会【纪中】【前缀和】
2019-03-04
纪中2020.3.18普及C组模拟赛总结
2019-03-04
YbtOJ 递推算法课堂过关 例5 平铺方案【递推(简单DP)】
2019-03-04
YbtOJ hash和hash表课堂过关 例1 字符串哈希【hash】
2019-03-04
CSUST 2021 周赛 2 题解
2019-03-04
前后端数据交互之表单
2019-03-04
剑指offer JZ15 反转链表
2019-03-04
剑指offer JZ21 栈的压入弹出序列
2019-03-04
剑指offer JZ31 整数中1出现的次数
2019-03-04
实现基于scrapy框架的天气预报爬虫hengYangSpaider @572311文
2019-03-04
maven打包指定名称并去除jar-with-dependencies后缀
2019-03-04
Netty4服务端入门代码示例
2019-03-04
java连接mysql,jdbc驱动
2019-03-04
C++中的static成员函数以及static成员变量详解
2019-03-04
操作系统前传第六课--开发中的辅助工具
2019-03-04
Linux系统编程44 信号 - 信号的响应过程分析!!!
2019-03-04
win10正版系统安装 win10系统启动盘制作 小白装机第一步(U盘装机)
2019-03-04
VL53L0x TOF激光测距的 stm32 HAL库驱动代码
2019-03-04