#include <thread> #include <iostream> #include <string> using namespace std; /* 如果主线程执行完毕,就代表整个进程执行完毕了,此时 一般情况下,如果其他子线程还没有执行完毕,那么这些子线程也会被 操作系统强行终止; 所以如何想保持子线程运行状态的话,那么大家要让主线程保持执行; 例外情况下,以下介绍; */ thread mytoj(myprint); //join阻塞主线程,并等待主线程执行完毕; myobj.join(); /* 传统多线程主程序要等待子程序执行完毕,然后自己再最后退出; detach:分离,也就是主线程不和子线程会和了,主子线程分开执行; 一旦detach(),这个子线程就与主线程失去关联; 这个子线程就相当于被C++运行时库接管,执行完毕后, 由运行时库负责清理相关的资源。(守护进程)、 一旦调用了detach(),就不能再用join(),否则则系统会报错; joinable():判断是否可以成功使用join()或者detach()的; */ thread myobj(myprint); if (myobj.joinable()) { } else { } /* 其他创建线程的方法: 1,使用lambda表达式 */ auto mylamthread = [] { cout << "my thread begin1" << endl; cout << "mythread end1" << endl; } thread myobj4(mylamthread); myobj4.join();//or myobj4.detach(); //类的形式调用多线程 class TA { public: void operator()()//不能带参数 { cout << "my thread begin" << endl; cout << "my thread end" << endl; } }; //主函数中调用; TA ta; thread myobj3(ta); myobj3.join();//等待子线程执行结束 //错误示范:注意:如果主线程中先结束的话,子线程 //回到回台执行的话,打印语句可能无法打印部分或者全部语句; //大坑提示: /* m_i使用的是myi,当主线程结束后,myi被删除, 而m_i是一个引用,所以会导致打印不可预料; */ class TA { public: int& m_i; TA(int& i) :m_i(i) {} void operator()()//不能带参数 { cout << "my thread begin" << m_i << endl; cout << "my thread end" << m_i << endl; } }; //主函数中调用; int myi = 6; TA ta(myi); thread myobj3(ta); myobj3.detach(); /* 问题:以上主线程结束,ta创建的对象也被释放,这时调用 对象的程序函数不会出错嘛? 答:此时对象ta是被复制到线程中去的,所以执行完线程后ta被销毁, 但是复制的ta对象依然存在,所以没有问题; 前提是:这个TA这个类没有引用或者指针类型的对象, 可以通过修改类的成员函数来查看; */ class TA { public: int& m_i; TA(int& i) :m_i(i) {} TA(const TA& ta) : m_i(ta.m_i) { } ~TA() { } void operator()()//不能带参数 { cout << "my thread begin" << m_i << endl; cout << "my thread end" << m_i << endl; } }; //主函数中调用; int myi = 6; TA ta(myi); thread myobj3(ta); myobj3.detach(); //正确写法:不能使用引用:如下: class TA { public: int m_i; TA(int i) :m_i(i) {} TA(const TA& ta) : m_i(ta.m_i) { } ~TA() { } void operator()()//不能带参数 { cout << "my thread begin" << m_i << endl; cout << "my thread end" << m_i << endl; } }; //主函数中调用; int myi = 6; TA ta(myi); thread myobj3(ta); myobj3.detach(); //第二节课 using namespace std; /* 分析认为,i此时是拷贝的内容,是值不是引用;是值传递,因此使用detach()时 没有问题;但是不推荐使用这个方式; detach()使用指针,绝对有问题,比如以下pmybuf; 那如何传递字符串呢? */ void myprint(const int& i, char* pmybuf) { cout << i << endl; cout << pmybuf << endl; return; } int main() { //一,传递临时对象作为线程参数 //要避免的陷阱 int mvar = 1; int& mvary = mvar; char mybuf[] = "this is a test!"; thread myobj(myprint,, mvar(, mybuf); myobj.join(); //myobj.detach();// } /* 修改为如下情况下是否可以呢? 事实上,存在可能性:mybuf都被回收了,即 main()函数执行完了,系统才把mybuf转化为string对象; const string &pmybuf,其实不是使用的引用,而是调用了拷贝构造函数; 重新创建了一个对象; */ void myprint(const int& i, const string &pmybuf) { cout << i << endl; cout << pmybuf.c_str() << endl; return; } int main() { int mvar = 1; int& mvary = mvar; char mybuf[] = "this is a test!"; //使用隐式转换是不安全的; thread myobj(myprint, , mvar, mybuf);//错误 //在创建线程的同时构造临时对象的方法传递参数是可行的; thread myobj(myprint, , mvar, string(mybuf));//修改为正确的方法 //myobj.join(); myobj.detach();// cout << "i love china"; } //总结: /* 若传递int这种简单类型参数,建议都是值传递,不要使用引用传递; 如果传递类对象,避免隐式类型转化;全部都在创建线程这一行就构造出来; 最好使用join(),而不要使用detach()函数; 心得:如果对一些问题不清楚的话,可以使用测试的方法来检测一些 东西是否如推测的那样; */ /* 每个线程不官是主线程,都有一个id,库函数中id的方法: std::this_thread::get_id(); */ //此时传递的是对象的拷贝,不是真正的引用类型; void myprint(A& pmybuf) { pmybuf.m_i = 100; cout << "fee" << "thread_id" << std::this_thread::get_id() << endl; } int main() { A myobj(10);//生成一个对象 //std::ref()此时是真正的对象的引用类型;不会再被拷贝出来一份; std::thread mytobj(myprint2, std::ref(myobj)); mytobj.join(); return 0; } //move使用 void myprint2(unique_ptr<int> pzn) { } int main() { unique_ptr<int> myp(new int(100)); std::thread mytobj(myprint2, std::move(myp)); mytobj.join(); return 0; } //用成员函数指针做线程函数:的例子: //thread_work是类A的成员函数; int main() { A myobj(10); std::thread myobj(&A::thread_work, myobj, 15);//15是线程函数的参数; myobj.join(); return 0; } 或者: class A { void operator()(int num) { } }; int main() { A myobj(10); //此时调用的函数是上述的operator()(int num)函数; //不调用拷构造函数了;如果调用detach()就不安全了;容 std::thread myobj(std::ref(myobj),15); myobj.join(); } //多个线程处理问题 void myprint(int num) { cout << "myprint start run:" << num << endl; //.... cout << "myprint end" << num << endl; } int main() { //创建和等待多个线程 vector<thread> mythreads; //a,多个线程执行顺序是乱的,和操作系统的运行调度机制有关系; //主线程等待所以子线程运行结束,最后主线程运行结束,老师推荐这种join的写法 //便于对大量线程的管理; for (int i = 0; i < 10; i++) { mythreads.push_back(thread(myprint, i)); } for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) { iter->join();//等待10个线程都返回 } cout << "i love china;"; } //数据共享问题分析 vector<int> g_v = { 1,2,3 }; //共享数据;只读数据是安全稳定的,不需要特别的处理,直接读就可以了; //有读有写,如何处理呢? //最简单的处理:读的时候不能写,写的时候不能读。两个线程不能同时写,8个线程不能同时读; //由于任务切换导致各种诡异事情发生,最可能的诡异事情是 程序崩溃; class A { public: //把收到的消息(玩家命令)入到一个队列的线程 void inMsgRecvQueue() { for (int i = 0; i < 10000; ++i) { cout << "inMsgRecvQueue" << i << endl; msgRecvQueue.push_back(i); } } void outMsgRecvQueue() { for (int i = 0; i < 10000; ++i) { if (!msgRecvQueue.empty()) { //消息不为空 int command = msgRecvQueue.front(); msgRecvQueue.pop_front(); //这里考虑处理数据; } else { //消息为空 } } } private: std::list<int> msgRecvQueue; }; int main() { A myobja; //第二个参数是引用,才能保证线程里用的是同一个对象 std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobja); std::thread myInMsgObj(&A::inMsgRecvQueue,&myobja); myInMsgObj.join(); myOutnMsgObj.join(); } //保护共享数据,操作时某个线程,用代码把共享数据锁住,操作数据,解锁; //其他操作数据的线程必须等待解锁,锁定住,操作,解锁; //“互斥量” //互斥量mutex概念;互斥量是类对象,理解成一把锁; //多个线程尝试用lock()成员函数加锁这这把锁头,只有一个线程能够锁定成功, //只有一个线程能锁定成功,(成功的标志) //如果没锁成功,那么流程卡在lock处不断尝试去加锁这把锁; /* std::mutex m_Mutex; m_Mutex.lock(); m_Mutex.unlock(); //m_Mutex是std::mutex的成员; std::lock_guard 类模板; ex: std::lock_guard<std::mutex> sbguard(m_Mutex); 死锁这个问题,是由至少两个锁头也就是两个互斥量才能产生; 比如两个线程A,B分别锁住了其中一把锁,但是需要另一个把锁 但是另一个把锁在另一个线程中,所以产生相互等待对方的锁 被解锁才能使用; */ /* 死锁的一般解决方案: 1,互斥量的顺序不搞乱,保持一致; std::lock_guard<std::mutex> sbguard1(m_Mutex1); std::lock_guard<std::mutex> sbguard2(m_Mutex2); 或者: m_Mutex1.lock(); m_Mutex2.lock(); m_Mutex1.unlock(); m_Mutex2.unlock(); std::lock()函数模板: 能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不行, 1个也不行); //它不存在这种因为再多个线程中,因为锁的顺序问题导致死锁的风险; std::lock():如果互斥量中有一个没有锁住,它就等在那里, 等多有互斥量都锁住,它才往下走; 如果一个没有锁住,它会释放锁住的互斥量,然后再次去锁住所有的 互斥量,直到所有的都被锁住。 如果一个没有锁住,它就释放锁住的互斥量解锁;然后去锁定另一个, 待会再锁定先前解锁的互斥量; 同时锁定多个互斥量的场景; example: std::lock(my_mutex1,my_mutex2); my_mutex1.unlock(); my_mutex2.unlock(); 或者: std::lock(my_mutex1,my_mutex2);//相当于每个互斥量都调用了.lock(); std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock); std::lock_guard<std::mutex> sbguard2(my_mutex2,std::adopt_lock); 注释:使用std::adopt_lock表示不会再在对象的构造函数中再次锁定my_mutex1,my_mutex2; std::adopt_lock是个结构体对象,起一个标记作用,作用是 表示结构体对象不需要再lock(); 一次锁定多个互斥量; unique_lock取代lock_guard unique_lock是个类模板,工作中,一般lock_guard(推荐使用) lock_guard取代了mutex的lock()和unlock(); std::unique_lock<std::mutex> sbguard1(my_mutex1); std::lock_guard<std::mutex> sbguard1(my_mutex); 以上两种情况下可以互换; std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock);//adopt_lock标记作用 std::adopt_lock:表示这个互斥量已经被lock了(你必须要把互斥量提前lock了,否则会报异常); std::adopt_lock标记的效果是“假设调用方线程已经拥有了互斥量的所有权(已经lock()成功了) 通知lock_guard不需要再构造函数中lock这个互斥量了; unique_lock也可以带这个标记:std::adopt_lock标记,含义相同; //休息20s std::chrono::milliseconds dura(20000);//20s std::this_thread::sleep_for(dura); // std::try_to_lock使用: std::try_to_lock尝试取锁,如果成功/失败立即返回,不会阻塞在那里, 用这个try_to_lock的前提是你自己不能先去lock; 但是前提之前不能加锁; 例如:my_mutex1.lock();此时使用错误; std::unique_lock<std::mutex> sbguard1(my_mutex1,std::try_to_lock); sbguard1.owns_lock()判定是否拿到锁; std::defer_lock: 前提是:你自己不能先lock,否则会报异常; defer_lock的意思就是并没有给mutex加锁,初始化一个没有加锁的mutex; std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock); sbguard1.lock();//咱们不能自己unlock() unique_lock的成员函数: lock(); unlock(); try_lock();尝试给互斥量加锁,如果拿不到锁,则返回false; 如果拿到锁了,返回true;这个函数不阻塞的; release(),返回它所管理的mutex对象指针,并释放所有权,也就是说,这个 unique_lock和mutex不再有关系;严格区分unlock()和release()的区别,不要混淆; 如果原来mutex对象处于加锁状态,你有责任接管过来并 负责解锁。(release返回的是原始mutex的指针); 既然咱们不能自己unlock(),那unlock()的作用 又是什么呢? 1,有时需要暂时解锁处理一些非共享的代码,执行完后可以再次 lock(); try_lock():使用方法; std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock); sbguard1.try_lock() == true; sbguard1.try_lock() == false; relase(),使用方法: std::unique_lock<std::mutex> sbguard1(my_mutex1); std::mutex* ptx = sbguard1.release(); ... ptx->unlock();自己负责mutex的unlock(); 注意:只有共享数据才需要加锁,不需要的地方千万不需要加锁 ; 因此要及时加锁,解锁; 有人也把锁住的代码多少,称为锁的粒度,粒度一般用粗细来描述; a/锁住的代码少,这个粒度叫细,执行效率高; b/锁住的代码多,粒度叫粗,那执行效率就低; 要学会尽量选择合适粒度的代码进行保护,粒度太细,可能漏掉共享数据的 保护,粒度太粗,影响效率; 选择合适的粒度,是高级程序员的能力和实力的体现; 转移所有权的两种方式: std::unique_lock<std::mutex> sbguard1(my_mutex1); std::unique_lock<std::mutex> sbguard2(std::move(sbguard1)); 方法2:返回所有权 std::unique_lock<std::mutex> rtn_unique_lock() { std::unique_lock<std::mutex> tmpguard(my_mutex1); return tmpguard; //从函数返回一个局部的unique_lock对象是可以的; 讲解过移动构造函数。返回这种局部对象tmpguard会导致系统生成临时 unique_lock对象,并调用unique_lock的移动构造函数; } 使用方法: std::unique_lock<std::mutex> sbguard = rtn_unique_lock(); */ /* 单例设计模式: std::mutex resource_mutex; std::once_flag g_flag;//这是个系统定义的标记 class MyCAS{ private: MyCAS(){} private: static MyCAS *m_instance; public: static MyCAS* GetInstance() { if(m_instance == NULL)//双重锁定(双重检查) { std::unique_lock<std::mutex> mymutex(resource_mutex);//自动加锁 if(m_instance == NULL) { m_instance = new MyCAS(); static CG c1; } } return m_instance; } class CG{ public : ~CG() { if(MyCAS::m_instance) { delete MyCAS::m_instance; MyCAS::m_instance = NULL; } } }; void func() { cout << "test" << endl; } }; MyCAS* MyCAS::m_instance = NULL; 使用: MyCAS *p = MyCAS::GetInstance(); //std::call_once():C++11引入的函数,该函数的第二个参数是一个函数名a(); //call_once()功能是能够保证函数a()只被调用一次; //call_once()具备互斥量这种能力,而且效率上,比互斥量消耗的资源更少; //call_once()需要 与一个标记结合使用,这个标记std::once_flag;其实once_flag是一个结构; call_once()就是通过这个标记来决定对应的函数a()是否执行,调用call_once()成功后, call_once()就把这个标记设置为一种已调用状态,那么对应的函数a(),就不会被调用了; 通过修改以上代码,可以如下使用: std::call_once(g_flag,CreateInstance);两个线程同时执行到这里,其中一个 线程要等待另一个线程执行完再执行; g_flag表示是否被执行过,如果执行过会被标记为1,接下来的线程不会再次执行CreateInstance(); 类中CreateIntance()如下: static void CreateInstance() { std::chrono::milliseconds dura(20000); std::this_thread::sleep_for(dura); m_instance = new MyCAS(); static CG cl; } static MyCAS* GetInstance() { std::call_once(g_flag,CreateInstance); cout << "ok " << endl; return m_instance; } */ /* 一,条件变量std:: condition_variable,wait(),notify_one(); 线程A:等待一个条件满足; 线程B:专门往消息队列中扔消息(数据) std::condition_variable实际上是一个类,是一个和条件相关的一个类, 说白了就是等待一个条件达成。这个类是需要和互斥量配合工作, 用的时候我们要生成这个类的对象; 如何避免通过不断的判断来执行某些语句,而通过通知函数或者条件达成 后 通知执行呢? 具体代码如下: class A { public: void inMsgRecvQueue() { for(int i = 0; i < 10000; i++) { std::unique_lock<std::mutex> sbguard1(my_mutex1); msgRecvQueue.push_back(i); //尝试把wait()的线程唤醒,那么OutMsgRecvQueue()就被 //唤醒了; my_cond.notify_one(); } } void outMsgRecvQueue() { int command = 0; while(true) { std::unique_lock<std::mutex> sbguard1(my_mutex1); //wait()用来等一个东西 //如果第二个参数lambda表达式是false,wait将解锁 //互斥量,并堵塞到本行; //那堵塞到什么时候呢?堵塞到其他线程调用notify_one() //成员函数为止; //如果wait()没有第二个参数,my_cond.wait(sbguard1),那么 //就和第二个参数表达式返回false的效果一样,wait()将 //解锁互斥量,并堵塞到本行,堵塞到其他某个线程调用notify_one() //成员函数为止; //当其他线程用notify_one()将本wait(原来是睡着/赌塞)的状态 唤醒后,wait就开始恢复干活了,恢复后wait干什么呢? a)wait不断地尝试重新获取互斥量锁,如果获取不到,那么流程就卡在 wait这里等待获取,如果获取到了锁(等于加了锁), b)如果wait有第二个参数(lambda),就判断这个表达式,如果表达式为 false,那么wait又对互斥量解锁。如果表达式为true,则wait返回, 流程继续,(此时互斥锁是被锁着的); c)如果wait没有第二个参数,则wait返回,流程走下来; 注意:只有wait()处于等待时,notify_one()才有效果,如果该线程正在 处理其他事情时,不是卡在wait()时,此时notify_one()也会没有效果。所以 不要想当然。以为只要notify_one()就可以。所以notify_one()要看其他线程是否 正处于wait()状态下; notify_one():唤醒一个线程; notify_all():我们尝试把wait()的线程唤醒,执行完这行, 那么outMsgRecvQueue()里面的wait就会被唤醒,唤醒之后的事情后续研究; //一个lambda就是一个可调用对象(函数) my_cond.wait(sbguard1,[this]{ if(!msgRecvQueue.empty()) { return true; } return false; }); } //流程执行都这里,这个互斥锁一定是被锁住的; command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在; msgRecvQueue.pop_front();//移除第一个元素,但不返回; sbguard1.unlock(); cout << "outMsgRecvQueue" << endl; private: std::list<int> msgRecvQueue; std::mutex my_mutex1; std::condition_variable my_cond;//生成一个条件变量对象 }; */ /* std::async,std::future创建后台任务并返回; 希望线程返回一个结果; std::async是个函数模板,用来启动一个异步任务, 启动起来一个异步任务后,他返回一个std::future对象, std::future 启动一个异步任务,就是自动创建一个线程并开始执行对应的入口函数, 他返回一个std::future对象,std::future对象里面就含有线程入口函数所 返回的结果,(线程返回的结果),我们可以通过调用future对象的成员 函数get()来获得结果; 将来的意思,有人也称呼std::future提供了一种访问异步操作结果的机制, 这个结果你可能没有办法马上拿到结果,线程执行完毕后你就能拿到结果; */ /* int mythread() { cout << "mythread() " << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); cout << "mythread end" << "threadid=" << std::this_thread::get_id() << endl; return 6; } int main() { cout << "main" << "threadid=" << std::this_thread::get_id() <<endl; std::future<int> result = std::async(mythread); cout << "continue" << endl; int def; def = 0; cout << result.get() << endl; //result.wait()等待线程结果返回,本身不返回结果值; cout << "I love china" << endl; return 0; } 如果用类的成员函数作为线程的入口如何做呢? 备注: A a; mythread:为类A的成员函数; int tmpar = 12;为整型参数; 第二个参数是对象的引用,它能保证对象线程使用的是同一个对象; std::future<int> result = std::async(&A::mythread,&a,tmpar); cout << result.get() << endl;//确保线程执行完毕; 我们通过额外向std::async()传递一个参数,该参数的类型是std::launch类型(枚举类型) 来达到一些特殊的目的; std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者 get()函数调用时才执行; 那如果wait()或者get()没有被调用,那么线程会执行吗?不会; 除非调用wait()或者get(); std::packaged_task:打包任务,把任务包装起来; 是个类模板,它的模板参数是各种可调用对象; 通过std::packaged_task来把各种可调用对象包装起来, 方便将来作为线程入口函数; 我们把函数mythread通过packaged_task包装起来; std::packaged_task<int(int)> mypt(mythread); 线程直接开始执行,第二个参数作为线程入口函数的参数; std::thread t1(std::ref(mypt),1); t1.join();//等待线程执行完毕; std::future<int> result = mypt.get_future();//std::future对象里包含有线程入口函数的返回结果, 这里result保存mythread的执行结果; cout << result.get() << endl; 使用lambda表达式作为线程的入口函数: std::packaged_task<int(int)> mypt([](int mypar){ cout << mypar << endl; cout << "mythread() start" << std::this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); cout << "mythread() end" << "threadid=" << std::this_thread::get_id() << endl; return 0; }); std::thread t1(std::ref(mypt),1); t1.join(); std::future<int> result = mypt.get_future(); cout << result.get() << endl; cout << "I love china" << endl; 总结:packaged_task对象本身也是一个可调用对象, 比如: mypt(106); std::future<int> result = mypt.get_future(); cout << result.get() << endl; 其他使用方法: vector<std::packaged_task<int(int)> > mytasks; mytasks.push_back(std::move(mypt)); std::packaged_task<int(int)> mypt2; auto iter = mytasks.begin(); mypt2 = std::move(*iter); mytasks.erase(iter); mypt2(123); std::future<int> result = mypt2.get_future(); cout << result.get() << endl; std::promise ,类模板; 通过在某个线程中给他赋值,然后我们可以再其他线程中,把这个值取出来; 总结:通过promise保存一个值,在将来某个时刻我们通过把一个future 绑定到这个promise上来得到这个绑定的值; void mythread(std::promise<int>&tmpp, int calc) { //做一系列复杂的操作 calc++; calc* = 10; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); int result = calc; tmpp.set_value(result); result; } void mythread2(std::future<int> &tmpf) { auto result = tmpf.get(); cout << "mythread2 result" << result << endl; return; } int main() { std::promise<int> myprom;//声明一个std::promise对象myprom,保存的类型为int; std::thread t1(mythread,std::ref(myprom),180); t1.join(); //获取结果值 std::future<int> ful = myprom.get_future(); auto result = ful.get(); cout << "result=" << result << endl; cout << "i love china" << endl; } */ /* std::future<int> result = std::async(mythread); std::future_status status = result.wait_for(std::chrono::seconds(1)); if(status == std::future_status::timeout)//等待线程一秒钟,希望其返回;但是线程等待时间为5秒钟,因此超时; { //超时,表示线程未执行完成; } else if(status == std::future_status::ready) { //线程成功执行完毕; } else if(status == std::future_status::deferred) { //std::future<int> result = std::async(std::launch::deferred,mythread);线程被延迟执行; //async的第一个参数为::std::async(std::launch::deferred,mythread);时满足条件; // } */ /* 为什么第二次get这个future得到一个异常,主要是因为get函数的设计,是一个移动语义; std::shared_future:也是一个类模板;get()函数是复制数据; 例如: std::future<int> result = mypt.get_future();//std::future,这个对象里含有线程入口函数 test: bool isCanValue = result.valid(); 方法1//std::shared_future<int> result_s(std::move(result)); 方法2: std::shared_future<int> result_s(result.share()); 通过get_future()返回值直接构造了一个shared_future对象; std::shared_future<int> result_s(mypt.get_future()); auto mythreadresult = result_s.get(); mythreadresult = result_s.get(); */ /* 原子操作:std::atomic; (3.2)原子操作概念引出范例; 互斥量:多线程编程中,保护共享数据,锁,操作共享数据,开锁; 有两个线程,对一个变量进行操作,这个线程读该变量值,另一个线程往 这个变量中写值; 大家可以把原子操作理解成一种,不需要用到互斥量加锁技术的多线程并发编程方式; 原子操作:是在多线程中,不会被打断的,程序执行片段, 原子操作,比互斥量效率上更胜一筹。互斥量的加锁一般是针对一个代码段(几行 代码,而原子操作针对的一般都是一个变量,而不是一个代码段; 一般是指不可分割的操作,要么完成,要么没完成,不可能是半完成状态; //int g_count = 0; 我们封装了一个int类型的对象,可以像其他类型一样使用它; std::atomic<int> g_mycount = 0; 使用方法可以如下: g_mycount++; 一般用于计数或者统计,(累计发送出去了多少个数据包等); 原子操作符:++,--,+=,-=,等; g_mycount = g_mycount + 1;//结果不对; std::async(); std::thread()如果系统资源紧张,那么可能创建线程就会 失败,那么执行std::thread()时整个程序可能崩溃; std::async():我们一般不叫创建线程,(解释async能够创建线程), 我们一般叫它创建,一个异步任务; 1)如果用std::launch::deferred来调用async会怎么样? deferred延迟调用,并且不创建新线程,延迟到future对象调用get() 或者wait()时候才执行mythread,如果没有调用get(),wait()不会创建; 2)std::launch::async:强制这个异步任务在新线程上执行, 这意味着,系统必须要给我创建出新线程来运行mythread(); 3)std::launch::async | std::launch::deferred; 这个|:意味着调用async的行为可能是创建新线程,并立即执行; 或者是没有创建新线程并延迟到调用result.get()才开始执行任务入口函数; 两者居其一; d)我们不带额外参数,只给async函数一个入口函数名; 其实,默认值应该是std::launch::async | std::launch::deferred; 换句话说,系统会自行决定是异步(创建新线程)还是同步(不创建新线程) 方式运行; //自行决定是啥 意思?系统如何决定是异步(创建新线程)还是同步(不创建新线程) 方式运行; std::async 和 std::thread区别: 1,std::thread创建线程,如果系统资源紧张,创建线程失败,那么整个程序会报异常崩溃; 2,std::thread创建线程的方式,如果线程返回值,你想拿到也不容易; 3,std::async:创建异步任务;可能创建也可能不创建线程;并且async调用方法很容易拿到线程 入口函数的返回值; //由于系统资源限制; 1,如果用std::async,一般就不会报异常不会崩溃,因为如果系统资源紧张导致无法创建 新线程的时候,std::async这种不加额外参数的调用,就不会创建新线程。 而是后续谁调用了result.get()来请求结果;那么这个异步任务mythread就运行在执行这条 get()语句所在的线程上; 如果你强制std::async一定要创建新线程,那么就必须使用:std::launch::async;承受的代价是系统资源紧张时,程序崩溃; std::async不确定性问题的解决 由于不带额外参数的std::async调用,让系统自行决定是否创建新线程; 这个问题的焦点在于 std::future<int> result = std::async(mythread);写法; 这个异步任务到底有没有推迟执行;(std::launch::async还是std::launch::deferred); std::future对象的wait_for函数,第10节讲过; 定义开关:#define __WINON_ #ifdef __WINON_ ... CRITICAL_SECTION my_winsec;//windows中的临界区,非常类似C++11中的mutex; #endif 使用临界区时需要先初始化: InitializeCriticalSection(&my_winsedc);//临界区初始化; 然后使用: #ifdef __WINON_ EnterCriticalSection(&my_winsec); msgRecvQueue.push_back(i); LeaveCriticalSection(&my_winsec); #else my_mutex.lock(); msgRecvQueue.push_back(i); my_mutex.unlock(); #endif windows:多次进入临界区 在同一线程中(不同的线程就会卡住等待),windows中的 “相同的临界区变量”代表的临界区的进入, 但是你调用了几次EnterCriticalSection,你就得调用几次LeaveCriticalSection(&my_winsec); C++11是不允许同一个线程的地方连续调用多次lock的; 自动析构技术: std::lock_guard<std::mutex> sbguard(my_mutex); class CWinLock { public: CWinLock(CRITICAL_SECTION *pCritmp) { m_pCritical = pCritmp; EnterCriticalSection(m_pCritical); } ~CWinLock() { LeaveCriticalSection(m_pCritical); } private: CRITICAL_SECTION* m_pCritical; }; 使用方法: CWinLock wlock(&my_winsec); std::mutex:独占互斥量,自己lock时别人lock不了; recursive_mutex:递归的独占互斥量;允许同一个线程,同一个 互斥量多次被lock(),效率上比mutex要差一些; recursive_mutex:也有lock,也有unlock(); 考虑代码是否有 优化空间; 递归次数据有限制,递归太多次可能报异常; 带超时的互斥量std::timed_mutex和std::recursive_timed_mutex std::timed_mutex:是带超时功能的独占互斥量; try_lock_for():参数是一段时间,是等待一段时间; 如果我拿到了锁,或者等待超时时间没有拿到锁,就走下来; try_lock_until():参数是一个未来的时间点, 在这个未来的时间没到的时间内,如果拿到了锁,那么就走下来; 如果时间到了,没拿到锁,程序流程也走下来; std::recursive_timed_mutex:带超时功能的递归独占互斥量; (1/1)补充一些知识; wait(),notify_oen(),notify_all(); cout << atom << endl;//atom读是一个原子操作,整个这一行不是一个原则操作; class A { public: atomic<int> atm; A() { atm = 0; //auto atm2 = atm;//不允许; //atomic<int> atm3 = atm;//这种定义时初始化操作不允许, //load:以原子方式读atomic对象的值 atomic<int> atm2(atm.load());//读 auto atm3(atm.load()); atm2.store(12);//原子操作写; atm2 = 12; } }; 线程池: 场景: 服务端程序等待线程连接,每来一个客户端,就创建一个新线程为该客户提供服务; 1)网络游戏;两万个玩家,不可能给每个玩家创建个新线程,此程序写法在这种场景下不通; 2)程序稳定性问题; 在程序启动后,我一次性的创建好一定数量的线程,更好地放心,觉得程序代码更稳定; 线程创建数量: */