>博客是一个拾忆的过程,无形中节约了你的生命<

两个或更多的任务(独立的活动)同时发生(进行):一个程序同时执行多个独立的任务。

一、线程的创建

1.1、使用类的成员函数作为线程入口

#include <iostream>
#include <thread>
#include <mutex>
class MyThread {
public:MyThread() {};~MyThread() {};//线程相关void ThreadFunction() {//线程入口函数int i = 0;while (true) {std::this_thread::sleep_for(std::chrono::milliseconds(100));std::cout << "th_prt i = " << ++i<<",th_id = " << std::this_thread::get_id() << "\r\n";//退出循环判断if (mtx.try_lock()) {if (!th_status) {mtx.unlock();break;}mtx.unlock();}}std::cout << "thread exit" << "\r\n";}std::mutex mtx; //定义互斥量bool th_status = true; //线程状态
private:
};
int main()
{MyThread th;std::thread mytobjob(&MyThread::ThreadFunction, &th);//创建线程并运行std::this_thread::sleep_for(std::chrono::milliseconds(500));//延时0.5s//一、让线程退出循环th.mtx.lock();th.th_status = false;th.mtx.unlock();//二、阻塞主线程并等待子线程退出<因为子线程循环已结束,这里的阻塞时间几乎为零>mytobjob.join();std::cout << "END\r\n";
}
/***************************************************************************************/
说明:
01、thread ->是标准库里面的类。
02、std::thread mytobjob(func); ->创建线程mytobjob并执行,传入函数名作为线程开始入口。
03、mytobjob.join(); ->阻塞主线程并等待子线程结束(注意它是个结束线程的函数)。
04、线程的函数执行完毕,线程并没有完全退出,只是停止了而已。
05、子线程安全退出步骤总结:设置标志位让子线程函数退出循环->调用join()函数结束子线程。
06、必须先让子线程退出循环再调用join()函数,因为join()函数会阻塞主线程。
07、主线程默认使用main()作为入口函数。
08、mytobjob.detach(); ->子线程与主线程分离,两者不在关联,主线程退出后,由后台接管。(注意这句        话,可能会让人误解,主线程结束时会退出进程,detach()的线程也会被系统回收,除非你阻止主线程 退出进程。总之进程一旦结束,其下的线程都会被回收)
09、mytobjob.joinable(); ->返回true和false,判断是否可以.join(),比如线程已经退出,就会返回false。
/***************************************************************************************/

1.2、使用类的重载运算函数作为函数入口点

<---------------------------------------------------------------------------->
1、mythread.h
#ifndef _MYTHREAD_H
#define _MYTHREAD_H
#include <iostream>
#include <mutex>
class MyThread {
private:public:MyThread(); ~MyThread();//线程相关std::mutex mtx;bool thread_flag = true; //线程跳出循环//重载运算符作为线程入口void operator()()//注意此函数执行时会拷贝一份类对象,所以并不与类公用成员变量{                //而是复制出来的,一般不用这种方式int i = 0;while (true){std::this_thread::sleep_for(std::chrono::milliseconds(500));std::cout << "thraad prt i =" << i << "\r\n";i++;//线程退出循环判断this->mtx.lock();if (this->thread_flag == false){this->mtx.unlock();break;} this->mtx.unlock();}std::cout << "thraad exit"<< "\r\n";}
};
#endif
<---------------------------------------------------------------------------->
2、mythread.cpp
#include "mythread.h"
//构造函数
MyThread::MyThread(){std::cout<<"constructor prt:"<<"pcls = "<<this<<"\r\n";
}
//析构函数
MyThread::~MyThread(){std::cout<<"destructor prt:"<<"pcls = "<<this<<"\r\n";
}
<---------------------------------------------------------------------------->
3、main.cpp
#include <iostream>
#include <thread>
#include "mythread.h"
int main()
{MyThread th;std::thread mytobjob(th);//一、让线程退出循环th.mtx.lock();th.thread_flag = false; //注意此句并不能让线程退出循环//因这个是对象的成员,线程函数使用的是它自己复制出来的//所以程序的运行现象会一直阻塞th.mtx.unlock();//二、阻塞主线程并退出mytobjob.join();std::cout << "END\r\n";
}
<---------------------------------------------------------------------------->
01、要想使线程操做原对象的成员变量,可以用引用或指针,让引用在构造函数时绑定成员变量,因为引用复制后它绑定的还是原来的那个变量。(指针复制一份,它指向的内存不变)
02、注意,用指针和引用在修改原对象的成员变量时,必须保证原对象没有被析构,否则会产生严重后果。
03、可以自己定义拷贝构造函数来确定怎么拷贝。
04、重点:std::thread mytobjob(std::ref(th));//以引用的形式作为形参,可以解决上述问题这种写法只能用mytobjob.join();退出,因为子线程中使用的是主线程中的对象
05、引用和std::ref(th)在这里起到的效果不一样引用:先复制一份原对象,引用绑定到赋值出来的对象身上    std::ref(th):返回reference_wrapper类型,注意这个和引用&是有区别的,其可以阻止中间变量的    产,使用方法和引用一样,也可以赋值给引用类型。    生,感兴趣的可以查查相关资料。

1.3、用lambda表达式作为线程入口

#include <iostream>
#include <thread>
#include "mythread.h"
int main()
{auto mylamthread = []{std::cout << "我的lam线程开始执行\r\n";//.......std::cout << "我的lam线程执行结束\r\n";};std::thread mytobjoblam(mylamthread);mytobjoblam.join();return 0;
}
//单独一个函数作为线程入口的方法与此相同

二、线程的传参

2.1、主线程隐式创建临时对象的问题

#include <iostream>
#include <thread>
#include "mythread.h"
int main()
{auto mylamthread = [](const int &i,char *pmybuf){std::cout <<"形参 i = "<<i<<"\r\n";std::cout <<"形参 pmybuf = "<< pmybuf<<"\r\n";};int mvar = 1;int& mvary = mvar;char mybuf[] = "this is a test";std::thread mytobjoblam(mylamthread,mvar,mybuf);mytobjoblam.join();
}

A:看似没有问题的传递,但暗藏玄机,其实线程函数i绑定的并不是mvar,而是先拷贝一份mvar,而i绑定的拷贝的那个变量。(线程函数一般会搞复制这一套,其实是为了避免主线程退出之后,释放了变量,而访问了不可知的变量=>引用不是真引用、但保证了线程的安全)

B:需要注意的是第二个指针类型形参pmybuf和传入却是一个地址,因为无论指针怎么拷贝,指针指向的地址是不变的,但同时这种操作很危险,一定要要确保主线程结束之后,再释放指针指向的变量,注意指针就是一个变量而已。(=>指针是真指针,但不能保证线程的安全)

C:变量的传递就不用说了,因为我们都知道,变量的传参就是拷贝而已,而不是传入本身。

C:断点调试使用说明:shift+F9

2.2、传参时发生的隐式类型转换问题

#include <iostream>
#include <thread>
#include <string>
#include "mythread.h"
int main()
{auto lamfunc = [](const int& i,const std::string &strx) {std::cout << "形参 i = " << i << "\r\n";std::cout << "形参 pmybuf = " << strx.c_str() << "\r\n";std::this_thread::sleep_for(std::chrono::milliseconds(500));};int mvar = 1;int& mvary = mvar;char str[] ="this is a test";std::thread mythread(lamfunc, mvar, str);mythread.join();std::cout << "The main thread end!\r\n";
}
//1、str的数组传入,类型不匹配就会被隐式转换产生中间变量,所以str和strx的地址不一样,线程是安全的。
//2、这个程序看似没问题,但是还是存在一个隐藏很深的bug........隐式转换是需要时间的,如果转换还没完成,str就被回收了,这个时候就会出现不可预料的结果。

陷阱二的解决办法

#include <iostream>
#include <thread>
#include <string>
#include "mythread.h"
int main()
{auto lamfunc = [](const int& i,const std::string &strx) {std::cout << "形参 i = " << i << "\r\n";std::cout << "形参 pmybuf = " << strx.c_str() << "\r\n";std::this_thread::sleep_for(std::chrono::milliseconds(500));};int mvar = 1;int& mvary = mvar;char str[] ="this is a test";std::thread mythread(lamfunc, mvar, std::string(str)); //创建的时候就转换mythread.join();std::cout << "The main thread end!\r\n";
}
/**创建的时候就转换,让类型转换在主线程中完成。*陷阱二产生的根本原因,是转换在子线程中完成,而转换的条件是被转换的对象是存在的。*如果先转换,后执行线程,就不会出现此类问题。*总结1:线程的传的参数要在线程执行前准备完成,因为创建线程到线程成功运行中间应该是一个很短的时间,所以在主线程和从线程没共享资源的条件下,应尽快让两者脱离关系,不要在创建的时候拖泥带水,搞一堆事。(隐式转换就属于拖泥带水,非要在哪个间隙处理问题,资源提前准备好不可以吗?)*总结2:存在类型转换时,在创建线程时构造临时对象,形参使用引用类型。(重点、重点、重点)*/

2.3、使用线程ID查看隐式类型转换在哪个线程中执行

C++标准库函数提供了线程ID的获取方法:std::this_thread::get_id()

示例代码一:
#include <iostream>
#include <thread>
#include <string>
#include "mythread.h"
class A {
public:int m_i;A(int a) :m_i(a) {std::cout << "[A::A(int a)构造执行]," << this << ",thread_id=" << std::this_thread::get_id() << "\r\n";}A(const A& a) :m_i(a.m_i) {std::cout << "[A::A(const A)拷贝构造执行]," << this << ",thread_id =" << std::this_thread::get_id() << "\r\n";}~A() {std::cout << "[A::~A()析构执行]," << this << ",thread_id =" << std::this_thread::get_id() << "\r\n";}
};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";auto lamfunc = [](const A &clsx) {std::cout << "子线程的参数clsx的地址 = "<<&clsx << ",thread_id =" << std::this_thread::get_id() << "\r\n";};int mvar = 1;std::thread mythread(lamfunc,mvar); //mvar隐式类型转换成A类型,会调用构造函数进行类型转换mythread.join();std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}

示例代码二
#include <iostream>
#include <thread>
#include <string>
#include "mythread.h"
class A {
public:int m_i;A(int a) :m_i(a) {std::cout << "[A::A(int a)构造执行]," << this << ",thread_id=" << std::this_thread::get_id() << "\r\n";}A(const A& a) :m_i(a.m_i) {std::cout << "[A::A(const A)拷贝构造执行]," << this << ",thread_id =" << std::this_thread::get_id() << "\r\n";}~A() {std::cout << "[A::~A()析构执行]," << this << ",thread_id =" << std::this_thread::get_id() << "\r\n";}
};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";auto lamfunc = [](const A &clsx) {std::cout << "子线程的参数clsx的地址 = "<<&clsx << ",thread_id =" << std::this_thread::get_id() << "\r\n";};int mvar = 1;std::thread mythread(lamfunc,A(mvar)); //mvar显示类型转换成A类型,创建临时对象,临时对象被拷贝一份给引用绑定,然后临时对象析构释放,mythread.join();std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}
/*

注意1:示例中可以得出结论,拷贝是安全的(C语言形参传递也是拷贝),类型转换不安全的。

所以注意尽量避免在子线程中进行类型转换。(重点、重点、重点)

注意2:关于我对线程传参的理解,如果你觉得认可就认可,不认可以去翻看linux操作系统源码,主线程不会把自己的变量直接进行传递,主线程会复制一份变量,把复制的这份变量交给子线程,子线程在执行的时候用这份复制的变量进行参数传递。

1、主线程变量a,拷贝一份 a_x                     :此时a与a_x属于主线程的作用域。
2、创建线程时,让a_x的作用域主线程分离  :此时a_x属于子线程作用域。
3、子线程执行线程函数,首先要把a_x传入,(注意变量传入会再次拷贝)。
4、所以形参一般设置成引用,这样可以避免再次拷贝一次。
5、a_x的生命周期是其在线程函数执行结束才会被析构掉。
以上的作用域只是本人便于理解乱说的,但这样更容易理解。

2.4、类作为形参传递,引用类型的形参必须加const的问题

1、为了线程安全,系统默认不允许修改拷贝的那份变量,所以引用类型不加const程序会报错。

2、临时对象不可修改,这是一个很强硬的原则问题。(修改也没啥意义,它都脱离主线程控制了)

3、临时对象虽然不能修改,但可以在传参时禁止主线程产生临时对象,方法如下:

#include <iostream>
#include <thread>
#include <string>
#include "mythread.h"
class A {
public:int m_i;A(int a) :m_i(a) {std::cout << "[A::A(int a)构造执行]," << this << ",thread_id=" << std::this_thread::get_id() << "\r\n";}A(const A& a) :m_i(a.m_i) {std::cout << "[A::A(const A)拷贝构造执行]," << this << ",thread_id =" << std::this_thread::get_id() << "\r\n";}~A() {std::cout << "[A::~A()析构执行]," << this << ",thread_id =" << std::this_thread::get_id() << "\r\n";}
};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";auto lamfunc = [](A &clsx) {std::cout << "子线程的参数clsx的地址 = " << &clsx << ",thread_id =" << std::this_thread::get_id() << "\r\n";};int mvar = 1;A v(mvar);std::thread mythread(lamfunc,std::ref(v));//方法std::ref()可避免向子线程传递变量时产生临时对象mythread.join();while (1);std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}
//注意:如果禁止产生临时对象,引用形参操作的就是主线的作用域的变量,几乎等于操作全局变量。
//注意资源共享问题,这可是一个真引用。
//std::ref(v):此方法可阻止线程传参时产生临时对象。

2.5、智能指针作为线程函数的形参

std::unique_ptr<int> myp(new int(100)); //这是C++基础,注意智能指针就是个模板类。
智能指针指向的是一块内存,如果让两个智能指针直接相等是语法上不允许的,因为一块内存不能被释放两次,需要先将指针转换为右值,才能使用,例如以下写法是错误的(编译器报错):

int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";auto lamfunc = [](std::unique_ptr<int> pzn) {std::cout << "子线程的参数clsx的地址 = " << *pzn << ",thread_id =" << std::this_thread::get_id() << "\r\n";};std::unique_ptr<int> myp(new int(100)); //创建一个独占式智能指针myp指向一个new出来的int空间。std::thread mythread(lamfunc,myp);mythread.join();std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}

为说明右值问题,如下先进行一次C++11右值基础的复习,加深印象。

左值:(lvalue) => 存放在内存空间,有确切的地址。
右值:(rvalue) => 不存放在内存空间,没有确切的地址。
std::move(变量);//返回传入参数的右值引用。对与左值和右值,请参考C++基础。//注意std会对变量造成破坏,故使用std::move后,变量尽量不要使用了。
复习零、类初始化和赋值是完全不同的两种概念class A{}      //定义一个类A cls1();      //初始化操作(调用的是构造函数)A cls2 = cls1; //初始化操作(调用的是构造函数)cls2 = cls1;   //赋值操作(调用的是赋值运算符)对于引用,初始化(也叫绑定)和赋值也是不同的概念,而且引用必须初始化。初始化之后的其他操作都是对其被绑定对象的操作以上我感觉对于学C++的人来说,意识到这点很重要。
复习一、三五法则对于C++11之前的版本,适用三法则,C++11,及其之后的版本适用五法则。(法则请百度)C++11的五法则做如下说明:默认赋值运算符函数是浅拷贝。移动构造函数一旦创建,默认赋值运算符函数将不复存在。移动构造函数要和赋值运算符构造函数一起创建(不使用赋值操作,可以忽略其函数不写)一般拷贝构造函数写深拷贝代码,移动构造函数写浅拷贝代码(但无论怎样,他们只能参与初始化操作)大白话:浅拷贝就是把对象的资源掏空,被掏空的对象就不要在使用了。
复习二、左值和右值int a = 1;     //a是一个左值,1是一个右值int b = 2;     //b是一个左值,2是一个右值int c = a + b; //+需要右值,所以a和b都转换成右值,并且返回一个右值函数和匿名对象都属于右值,右值也叫将亡值。左值能转换为右值,右值却不能转换为左值int arr[] = {1, 2};int* p = &arr[0];*(p + 1) = 10; //p+1是一个右值,但是*(p+1)是一个左值p+1 = 10;      //错误
复习三、左值引用和右值引用const int    与  int const   没有区别const int &  与  int const & 没有区别交换次序只有在指针时才有区别=>(int const * 和const int *有区别)int & 只能绑定左值int && 只能绑定右值,(注意右值也叫即将销毁的对象)const int & 左值和右值都可以绑定(const int &会将右值的生命周期延长到和自己的一样)右值引用是为了移动构造函数产生的,其实就是为了浅拷贝一个右值的资源,右值作为将亡值,毕竟属于即将销毁的值,但有时候我们不希望销毁,比如函数返回值,所以只能将其拷贝到一个变量中,然后其被析构,这中间的拷贝其实是一种浪费,移动构造函数可以避免拷贝带来的浪费。但移动构造也能拷贝左值,注意左值不是将亡值,如果对其进行移动构造,之后将以不要在使用这个左值。
复习五、匿名对象被扶正的问题匿名对象,使用匿名对象做初始化时,不执行拷贝,而是被扶正(匿名对象和函数都属于右值)例如:std::string str = std::string("haut school"); //右边的匿名对象会被扶正
复习六、移动构构造函数C++11新特性,右值引用类型 int &&,右值引用顾名思义是可以绑定右值的引用。旧版本的某些情况下,对象在拷贝之后直接销毁了,出现了很多的不必须要的拷贝等问题。所以,C++11引入了移动操作,右值引用就是为了支持对象移动而产生的。重点:右值引用其实就是为了偷一个对象的值,而非复制它。移动构造函数对传入的对象会造成一定程度的破坏。
复习七、指针定义指针的时候一定养成初始化的习惯,例如:char*p = nullptr;删除指针内存够一定养成初始默认的习惯,例如:if(p!=nullptr){ delete p;p=nullptr; }
代码示例:
#include <iostream>
class Buffer {
public:int i = 99;char* buf = nullptr;   Buffer() {//构造1、无参构造函数buf = new char[10];buf[0] = 0; buf[1] = 1; buf[2] = 2;std::cout << "Buffer() this = " << this << "\r\n";}Buffer(const Buffer& cls_in):i(cls_in.i) {//构造2、拷贝构造(深拷贝)this->buf = new char[strlen(cls_in.buf)+1]; strcpy_s(this->buf, strlen(cls_in.buf) + 1,cls_in.buf);std::cout << "Buffer(Buffer& param)" << this << "\r\n";}Buffer(Buffer&& cls_in) noexcept :i(cls_in.i) {//构造3、移动构造(移动拷贝)(注意此构造会删除默认赋值运算符函数)this->buf = cls_in.buf;cls_in.buf = nullptr;std::cout << "Buffer(Buffer&& param)" << this << "\r\n";}Buffer& operator=(const Buffer& cls_r) {//拷贝赋值运算符函数if (this == &cls_r) return *this; this->i = cls_r.i;this->buf = new char[strlen(cls_r.buf) + 1];strcpy_s(this->buf, strlen(cls_r.buf) + 1, cls_r.buf);std::cout << "Buffer& operator=(const Buffer& cls_r)" << "\r\n";return *this;} Buffer& operator=(Buffer&& cls_r) noexcept {if (this == &cls_r) return *this;this->i = cls_r.i;delete[] this->buf; //斩断自己this->buf = cls_r.buf; //指向别人cls_r.buf = nullptr;//斩断别人return *this;}~Buffer() {//析构函数std::cout << "~Buffer() = " << this << "\r\n";if (buf != nullptr){delete[] buf;buf = nullptr;}}
};
int main()
{Buffer cls; cls.i = 9;Buffer cls1;cls1 = std::move(cls);std::cout << cls1.i << "\r\n";return 0;
}

经过上面的复习,大致可以得出解决办法,那就是使用std::move将原指针掏空就可以了。

int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";auto lamfunc = [](std::unique_ptr<int> pzn) {std::cout << "子线程的参数clsx的地址 = " << *pzn << ",thread_id =" << std::this_thread::get_id() << "\r\n";};std::unique_ptr<int> myp(new int(100)); //创建一个独占式智能指针myp指向一个new出来的int空间。std::thread mythread(lamfunc,std::move(myp));mythread.join();std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}注意,new出来的变量依然是在主线程中new的,属于主线程,子线程中的智能指针依然指向的是属于主线程的
内存,所以这里依然要保持子线程先退出,主线程才能退出。因为主线程退出会释放属于自己的变量。故不能使用mythread.detach();

三、并发与多线程

前四章是单个线程的创建方法和传参时的一些注意点,接下来在以上的基础上进行多个线程创建。

3.1、创建多个线程

创建十个线程,并放入到容器中

#include <iostream>
#include <thread>
#include <vector>
void myprint(int inum) {std::cout << "Run:线程编号=" << inum <<",线程ID="<< std::this_thread::get_id() << std::endl;std::cout << "Stop:线程编号=" << inum << ",线程ID=" << std::this_thread::get_id() << std::endl;
}
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";//--------------------------------------------------------------------------------------------//std::vector<std::thread> mythreads;//容器//创建10个线程,入口函数都使用void myprint(int inum) for (int i = 0; i < 10; i++) {mythreads.push_back(std::thread(myprint,i));}for (auto iter = mythreads.begin(); iter!= mythreads.end(); ++iter) {iter->join();}//--------------------------------------------------------------------------------------------//std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}

3.2、通用型互斥量

//所谓通用型互斥量,就是经常使用的独占式互斥量
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
//共享数据的保护案例<生产-消费模式>
//网络游戏服务器:
//     两个自己创建的线程,一个线程收集玩家发来的命令(用一个数字代表玩家发来的命令),并把命令数据写到一个队列中。
//     另一个线程从队列中去除玩家发来的命令,解析,然后执行相应的动作 。
//数据结构:vector,list。
//list和vector区别:<百度一下>
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;mtx.lock();msgRecvQueue.push_back(i); //i就是收到的命令mtx.unlock();}}bool outMsgLULProc(int& command) {//std::lock_guard<std::mutex> sbguard(mtx);//模板类,在析构的时候会调用mtx.unlock()//可以智能释放锁,但是没有mtx.lock();和mtx.unlock();灵活(具体用法可以百度)mtx.lock();if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();msgRecvQueue.pop_front();mtx.unlock();return true;}mtx.unlock();return false;}//线程2:把数据从消息列表中取出的线程(读,删)void outMsgRecvQueue() {int command = 0;for (int i = 0; i < 10000; ++i) {bool ret = outMsgLULProc(command);if (ret == true) {//链表非空->取元素成功std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;}else {//链表空->取元素失败std::cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << std::endl;}}}
private:std::mutex mtx;std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";//--------------------------------//A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();//--------------------------------//std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}

3.3、递归式互斥量

//同一个线程的同一个通用型互斥变量不能锁两次,但现实中有需要锁两次的场景,示例代码如下:
class A {
public:void testfun1() {std::lock_guard<std::mutex> sbguard(mtx);//访问共享资源 }void testfun2() {std::lock_guard<std::mutex> sbguard(mtx);//访问共享资源testfun1(); //此处会引发多次上锁异常}
private:std::mutex mtx;
};
//单独调用testfun1()和testfun2()时没有问题,但当两个函数互调时就会出现多次上锁的情况,此时程序
//会出现异常,为了解决此场景的问题,引入了递归式互斥量,示例代码如下:
class A {
public:void testfun1() {std::lock_guard<std::recursive_mutex> sbguard(mtx);//访问共享资源}void testfun2() {std::lock_guard<std::recursive_mutex> sbguard(mtx);testfun1(); //此处不会触发异常}
private:std::recursive_mutex mtx;
};
//说明1:std::recursive_mutex虽然可以多次锁定,但其执行效率没有std::mutex高。
//说明2:std::recursive_mutex的递归上锁次数有限制,超过限制时会触发异常。
//说明3:std::lock_guard<>是个类模板,用于构建区域锁,即对象创建构造时对传入的互斥量上锁,对象析构时对传入的互斥量解锁,用于实现自动释放锁的逻辑。

3.4、时间式互斥量

//所谓时间式互斥量就是带有超时功能的互斥量
//其有两个重要的方法:
//1、try_lock_for(); //参数是一个超时时间
//2、try_lock_unti(); //参数是未来的一个时间点
/***************************************************************************************/
//示例1:try_lock_for()的用法
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::chrono::milliseconds timeout(100);if (tim_mtx.try_lock_for(timeout)) {//100ms内上锁成功std::cout << "in...()拿锁成功,插入元素" << i <<"成功"<< "\r\n";msgRecvQueue.push_back(i);tim_mtx.unlock();}else {//100ms内上锁未成功,超时了std::cout << "in...()拿锁失败,未插元素" << i << "失败" << "\r\n";std::chrono::milliseconds dura(100);std::this_thread::sleep_for(dura);}}}bool outMsgLULProc(int& command) {tim_mtx.lock();std::this_thread::sleep_for(std::chrono::milliseconds(200));if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();//执行相应操作....msgRecvQueue.pop_front();tim_mtx.unlock();return true;}tim_mtx.unlock();return false;}//线程2:把数据从消息列表中取出的线程(读,删)void outMsgRecvQueue() {int command = 0;for (int i = 0; i < 10000; ++i) {bool ret = outMsgLULProc(command);if (ret == true) {//链表非空->取元素成功std::cout << "out...()执行,取出元素" << command << std::endl;}else {//链表空->取元素失败std::cout << "out...()执行,但目前消息队列为空"<< std::endl;}}}
private:std::timed_mutex tim_mtx;//时间式互斥量std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令
};
int main()
{std::cout << "The main start,thread_id = " << std::this_thread::get_id() << "\r\n";//--------------------------------//A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();//--------------------------------//std::cout << "The main end,thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}
/***************************************************************************************/
//示例2:try_lock_unti()的用法
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::chrono::milliseconds timeout(100);if (tim_mtx.try_lock_until(std::chrono::steady_clock::now()+ timeout)) {//到时间点(当前时间偏移100ms)、拿锁成功std::cout << "in...()拿锁成功,插入元素" << i <<"成功"<< "\r\n";msgRecvQueue.push_back(i);tim_mtx.unlock();}else {//100ms内上锁未成功,超时了std::cout << "in...()拿锁失败,未插元素" << i << "失败" << "\r\n";std::chrono::milliseconds dura(100);std::this_thread::sleep_for(dura);}}}bool outMsgLULProc(int& command) {tim_mtx.lock();std::this_thread::sleep_for(std::chrono::milliseconds(200));if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();//执行相应操作....msgRecvQueue.pop_front();tim_mtx.unlock();return true;}tim_mtx.unlock();return false;}//线程2:把数据从消息列表中取出的线程(读,删)void outMsgRecvQueue() {int command = 0;for (int i = 0; i < 10000; ++i) {bool ret = outMsgLULProc(command);if (ret == true) {//链表非空->取元素成功std::cout << "out...()执行,取出元素" << command << std::endl;}else {//链表空->取元素失败std::cout << "out...()执行,但目前消息队列为空"<< std::endl;}}}
private:std::timed_mutex tim_mtx;//时间式互斥量std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令
};
int main()
{std::cout << "The main start,thread_id = " << std::this_thread::get_id() << "\r\n";//--------------------------------//A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();//--------------------------------//std::cout << "The main end,thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}

3.5、互斥量的死锁

有两个锁A、B:

线程1的执行流程: =>锁A=>锁B=>执行代码=>释放锁

线程1的执行流程: =>锁B=>锁A=>执行代码=>释放锁

结果:两个线程都在无限期的等待对方释放锁,导致死锁。

示例一:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
//共享数据的保护案例<生产-消费模式>
//网络游戏服务器:
//     两个自己创建的线程,一个线程收集玩家发来的命令(用一个数字代表玩家发来的命令),并把命令数据写到一个队列中。
//     另一个线程从队列中去除玩家发来的命令,解析,然后执行相应的动作 。
//数据结构:vector,list。
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;mtx1.lock();mtx2.lock();msgRecvQueue.push_back(i); //i就是收到的命令mtx1.unlock();mtx2.unlock();}}bool outMsgLULProc(int& command) {//std::lock_guard<std::mutex> sbguard(mtx);//模板类,在析构的时候会调用mtx.unlock()//可以智能释放锁,但是没有mtx.lock();和mtx.unlock();灵活(具体用法可以百度)mtx2.lock();mtx1.lock();if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();msgRecvQueue.pop_front();mtx1.unlock();mtx2.unlock();return true;}mtx1.unlock();mtx2.unlock();return false;}//线程2:把数据从消息列表中取出的线程(读,删)void outMsgRecvQueue() {int command = 0;for (int i = 0; i < 10000; ++i) {bool ret = outMsgLULProc(command);if (ret == true) {//链表非空->取元素成功std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;}else {//链表空->取元素失败std::cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << std::endl;}}}
private:std::mutex mtx1;std::mutex mtx2;std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";//-------------------//A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();//-------------------//std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}
示例2:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
std::lock_guard sbguard(mtx);//这是一个模板类,<区域锁>
对象sbguard会在构造函数时调用mtx.lock()
对象sbguard会在构造函数时调用mtx.unlock()
此类可以省去在哪个位置解锁,但是由于其释放锁是在析构时,所以不是很灵活。
但是可以使代码更整洁,其实时牺牲灵活性和效率,来保证代码可读性。

死锁解决办法1:两个线程按照相同的顺序对互斥量上锁。

线程1:mtx1.lock();mtx2.lock();//执行....mtx1.unlock();mtx2.unlock();
线程2:mtx1.lock();mtx2.lock();//执行....mtx1.unlock();mtx2.unlock();

死锁解决办法2:使用std::lock()让程序自动决定上锁顺序。

线程1:std::lock(mtx1,mtx2);//执行....mtx1.unlock();mtx2.unlock();
线程2:std::lock(mtx1,mtx2);//执行....mtx1.unlock();mtx2.unlock();

死锁解决办法3:当程序的分支过多时unlock容易误写,使用模板类解决。

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
//共享数据的保护案例<生产-消费模式>
//网络游戏服务器:
//     两个自己创建的线程,一个线程收集玩家发来的命令(用一个数字代表玩家发来的命令),并把命令数据写到一个队列中。
//     另一个线程从队列中去除玩家发来的命令,解析,然后执行相应的动作 。
//数据结构:vector,list。
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;std::lock(mtx1,mtx2);std::lock_guard<std::mutex> sbguard1(mtx1,std::adopt_lock);//std::adopt_lock构造函数执行时,不调用lockstd::lock_guard<std::mutex> sbguard2(mtx2,std::adopt_lock);msgRecvQueue.push_back(i); //i就是收到的命令//mtx1.unlock();//mtx2.unlock();}}bool outMsgLULProc(int& command) {//std::lock_guard<std::mutex> sbguard(mtx);//模板类,在析构的时候会调用mtx.unlock()//可以智能释放锁,但是没有mtx.lock();和mtx.unlock();灵活(具体用法可以百度)std::lock(mtx1, mtx2);std::lock_guard<std::mutex> sbguard1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> sbguard2(mtx2, std::adopt_lock);if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();msgRecvQueue.pop_front();//mtx1.unlock();//mtx2.unlock();return true;}//mtx1.unlock();//mtx2.unlock();return false;}//线程2:把数据从消息列表中取出的线程(读,删)void outMsgRecvQueue() {int command = 0;for (int i = 0; i < 10000; ++i) {bool ret = outMsgLULProc(command);if (ret == true) {//链表非空->取元素成功std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;}else {//链表空->取元素失败std::cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << std::endl;}}}
private:std::mutex mtx1;std::mutex mtx2;std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";//--------------------------//A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();//--------------------------//std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}

总结:std::lock(mtx1, mtx2); //一次锁定多个互斥量,并自动决定上锁顺序(很少用)
           std::lock_guard<std::mutex> sbguard1(mtx1, std::adopt_lock);//自动解锁
           std::lock_guard<std::mutex> sbguard2(mtx2, std::adopt_lock);//自动解锁
建议:还是使用mtx.lock();、mtx.unlock();的形式。

3.6、独占锁的使用

互斥锁保证了线程间的同步,但是却将并行操作变成了串行操作,这对性能有很大的影响,所以我们要尽可能的减小锁定的区域,也就是使用细粒度锁
std::lock_guard:区域锁
std::unique_lock:独占式锁
两者具体区别和优缺点可以自行百度,unique_lock可以完全取代区域锁,但是有缺点。

用法一、lock_guard与unique_lock的替换std::lock_guard<std::mutex> sbguard(mtx);可以替换为.....std::unique_lock<std::mutex> sbguard(mtx);
/*==================================================================*/
用法二、lock_guard与unique_lock的替换mtx.lock();std::lock_guard<std::mutex> sbguard(mtx,std::adopt_lock);可以替换为.....mtx.lock();std::unique_lock<std::mutex> sbguard(mtx, std::adopt_lock);//注意:std::adopt_lock:代表传入的互斥量已经被加锁了
/*==================================================================*/
用法三、引出问题线程1:mtx.lock();std::lock_guard<std::mutex> sbguard(mtx,std::adopt_lock);线程2:mtx.lock();std::lock_guard<std::mutex> sbguard(mtx,std::adopt_lock);std::this_thread::sleep_for(std::chrono::milliseconds(5000));//注意:线程2的延时5s,不快速释放锁,导致线程1的上锁阻塞了5s//解决办法,线程1先尝试上锁,上锁不成功,立即返回,不阻塞。<见用法四>
/*==================================================================*/
用法四、使用try_to_lock形参=>尝试上锁线程1:std::unique_lock<std::mutex> sbguard(mtx, std::try_to_lock);//尝试加锁if (sbguard.owns_lock()) {//加锁成功//操作共享数据...}else {//加锁失败//处理其他的事...}   线程2:    mtx.lock();//上锁std::unique_lock<std::mutex> sbguard(mtx,std::adopt_lock);//区域锁std::this_thread::sleep_for(std::chrono::milliseconds(10000));//延时10s//操作共享资源...//注意:使用try_to_lock时,自己不能提前去加锁,否则会卡死
/*==================================================================*/
用法五、使用defer_lock形参线程1:std::unique_lock<std::mutex> sbguard(mtx, std::defer_lock);//绑定一个未初始化的锁sbguard.lock();//需要手动初始化//操作共享资源...线程2:    mtx.lock();//上锁//操作共享资源...mtx.unlock();//解锁//注意:这些类模板的作用,都是绑定互斥量,在原来互斥量的基础上加了一些方法而已//比如类模板会在对象析构时对传入的互斥量解锁等等,也能通过对象手动的对传入的互斥量上锁解锁等
/*==================================================================*/
用法六、调用方法try_lock(),用法类似形参try_to_lock//在用法五的基础上修改线程1:std::unique_lock<std::mutex> sbguard(mtx,std::defer_lock);if (sbguard.try_lock() == true) {//操作共享资源...}else {//处理其他的事...}线程2:    mtx.lock();//上锁//操作共享资源...mtx.unlock();//解锁//注意:不用模板类绑定互斥量,原互斥量也可以使用此方法(注意使用unlock())
/*==================================================================*/
用法七、调用方法release(),返回模板类对象绑定的mutex指针,<解除unique_lock与mutex的绑定>//如果mutex已经加锁,接管过来之后,还是加锁状态线程1:std::unique_lock<std::mutex> sbguard(mtx);//sbguard与mtx绑定std::mutex* p_mtx = sbguard.release();//sbguard与mtx分离//操作共享资源...p_mtx->unlock();线程2:    mtx.lock();//上锁//操作共享资源...mtx.unlock();//解锁
/*==================================================================*/
说明1:std::adopt_lock:标识传入的互斥量已经上锁了。
说明2:std::defer_lock:标识传入的互斥量还没有上锁。
说明3:std::try_to_lock:标识对传入的互斥量尝试上锁,需要手工加锁。
说明4:lock()和unlock()之间的代码的段的大小,叫做锁的粒度,粒度越细越好。
说明5:同一个mutex不能绑定两个模板类对象。
说明6:换绑定,可以使用std::move将一个unique_lock对象里的mutex掏出来与另一个绑定在一起。
说明7:std::unique_lock<std::mutex>类型的函数,返回值时,调用的是移动拷贝构造函数。

3.7、单例模式的介绍

<前言:单例模式只是设计模式的一种,具体的其他模式还是需要学习的,因为是C++的知识,所以其他的设计模式,可以通过C++书籍或者百度等工具自行学习,这里只说单例模式>

所谓单例模式,就是设定一种类,其只能实例化一个对象,创建代码如下:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
//共享数据的保护案例<生产-消费模式>
//网络游戏服务器:
//     两个自己创建的线程,一个线程收集玩家发来的命令(用一个数字代表玩家发来的命令),并把命令数据写到一个队列中。
//     另一个线程从队列中去除玩家发来的命令,解析,然后执行相应的动作 。
//数据结构:vector,list。
class MyCAS {
public:static MyCAS* GetInstance() {if (m_instance == NULL) {m_instance = new MyCAS();static CGarhuishou cl;}return m_instance;}class CGarhuishou {//类中套类,用来释放对象public:~CGarhuishou() {if (MyCAS::m_instance) {delete MyCAS::m_instance;MyCAS::m_instance = NULL;}}};void prt() {std::cout << "hello" << std::endl;}
private:MyCAS() {//私有化构造函数std::cout << "构造执行" << std::endl;}~MyCAS() {//私有析构函数std::cout << "析构执行" << std::endl;}static MyCAS* m_instance;//静态成员变量
};
MyCAS* MyCAS::m_instance = NULL;//静态成员初始化
int main()
{MyCAS* cls = MyCAS::GetInstance();  //cls与cls1指向的是同一个对象(只能实例化一个对象)MyCAS* cls1 = MyCAS::GetInstance();return 0;
}

多个线程中实例化单例类,会导致共享资源冲突,产生两个对象的产生,需要做如下保护:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
//共享数据的保护案例<生产-消费模式>
//网络游戏服务器:
//     两个自己创建的线程,一个线程收集玩家发来的命令(用一个数字代表玩家发来的命令),并把命令数据写到一个队列中。
//     另一个线程从队列中去除玩家发来的命令,解析,然后执行相应的动作 。
//数据结构:vector,list。
std::mutex resource_mutex;
class MyCAS {
public://GetInstance()函数是被频繁被调用的,而m_instance的冲突只会发生在第一次创建的时候,后续不需要再互斥了//当第一次创建完成后,之后如果再调用GetInstance()都加锁的话,效率会低,故使用双重加锁模式,只在第一次创建时使用锁static MyCAS* GetInstance() {if (m_instance == NULL){//双重锁定<第一重>std::unique_lock<std::mutex> mymutex(resource_mutex);//if (m_instance == NULL) {//双重锁定<第二重>m_instance = new MyCAS();static CGarhuishou cl;}}return m_instance;}class CGarhuishou {//类中套类,用来释放对象public:~CGarhuishou() {if (MyCAS::m_instance) {delete MyCAS::m_instance;MyCAS::m_instance = NULL;}}};void prt() {std::cout << "hello" << std::endl;}
private:MyCAS() {//私有化构造函数std::cout << "构造执行" << std::endl;}~MyCAS() {//私有析构函数std::cout << "析构执行" << std::endl;}static MyCAS* m_instance;//静态成员变量
};
MyCAS* MyCAS::m_instance = NULL;//静态成员初始化void mythread(){//线程入口函数std::cout << "我的线程开始执行了" << std::endl;MyCAS* pa = MyCAS::GetInstance();std::cout << "我的线程执行完毕了" << std::endl;
}
int main()
{   std::thread mytobj1(mythread);//线程1std::thread mytobj2(mythread);//线程2mytobj1.join();mytobj2.join();return 0;
}

std::call_once():C++11引入,第二的形参是一个函数名,作用是保证传入的函数只被调用一次。
=>std::call_once()具备互斥的能力,而且效率上比互斥量消耗的资源更少,需要与一个标记std::once_flag结合使用,std::once_flag是一个结构,通过std::once_flag决定函数是否执行,函数调用成功后,std::once_flag会更改为另一个状态。示例代码如下:

std::once_flag g_flag;//定义一个未被置位的标记
class MyCAS {
private: static void CreateInstance() {//只会被执行一次的函数m_instance = new MyCAS();static CGarhuishou cl;}
public://GetInstance()函数是被频繁被调用的,而m_instance的冲突只会发生在第一次创建的时候,后续不需要再互斥了//当第一次创建完成后,之后如果再调用GetInstance()都加锁的话,效率会低,故使用双重加锁模式,只在第一次创建时使用锁static MyCAS* GetInstance() {//if (m_instance == NULL){//双重锁定<第一重>//    std::unique_lock<std::mutex> mymutex(resource_mutex);////    if (m_instance == NULL) {//双重锁定<第二重>//        m_instance = new MyCAS();//        static CGarhuishou cl;//    }//}std::call_once(g_flag,CreateInstance);return m_instance;}class CGarhuishou {//类中套类,用来释放对象public:~CGarhuishou() {if (MyCAS::m_instance) {delete MyCAS::m_instance;MyCAS::m_instance = NULL;}}};void prt() {std::cout << "hello" << std::endl;}
private:MyCAS() {//私有化构造函数std::cout << "构造执行" << std::endl;}~MyCAS() {//私有析构函数std::cout << "析构执行" << std::endl;}static MyCAS* m_instance;//静态成员变量
};
MyCAS* MyCAS::m_instance = NULL;//静态成员初始化
void mythread(){//线程入口函数std::cout << "我的线程开始执行了" << std::endl;MyCAS* pa = MyCAS::GetInstance();std::cout << "我的线程执行完毕了" << std::endl;
}
int main()
{   std::thread mytobj1(mythread);//线程1std::thread mytobj2(mythread);//线程2mytobj1.join();mytobj2.join();return 0;
}
//说明1:std::call_once()类似于双重锁定保证函数只被执行一次,实现的机制还是互斥。
//说明2:std::call_once()的效率比直接互斥高,比双重锁定的互斥略低一点。

3.8、std::wait()的使用

条件变量

<前言:以5.4中的程序为例,取出消息的线程中,循环检测队列中是否有消息,当没有消息时,线程再空转,实在是一种损耗。为了优化这个问题,让插入消息的线程通知取消息的线程就行了,没有收到消息,让其睡眠,有点像QT中的信号与槽机制>

std::condition_variable是一个模板类,其与互斥量组合使用。
std::unique_lock+std::mutex+std::condition_variable+wait()+notify_one()/notify_all()的使用,示例代码如下:

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
//共享数据的保护案例<生产-消费模式>
//网络游戏服务器:
//     两个自己创建的线程,一个线程收集玩家发来的命令(用一个数字代表玩家发来的命令),并把命令数据写到一个队列中。
//     另一个线程从队列中去除玩家发来的命令,解析,然后执行相应的动作 。
//数据结构:vector,list。
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;std::unique_lock<std::mutex> sbguard(mtx);//sbguard与mtx绑定msgRecvQueue.push_back(i); //再末尾插入一个元素,i就是收到的命令my_cond.notify_one();//尝试唤醒调用my_cond.wait()的那个线程}}void outMsgRecvQueue() {int command = 0;while (true) {std::unique_lock<std::mutex> sbguard(mtx);//wait()用来等一个东西//如果第二的参数即lambda表达式返回值是true,直接返回。//如果第二的参数即lambda表达式返回值是false,wait将解锁互斥量,并阻塞到本行,直到其他的某个线程调用notify_one()将其唤醒。//如果wait()没有第二个参数,wait将解锁互斥量,并直接阻塞到本行,并等待其他的某个线程调用notify_one()将其唤醒。//没有第二参数时,当wait()被唤醒时,它首先会不断的尝试重新获取互斥量,直到获取到之后,跳出wait(),往下执行。//当有第二参数时,当wait()被唤醒时,它首先会不断的尝试重新获取互斥量,直到获取到之后,执行lambda表达式...//...如果lambda表达式返回false=>解锁,睡眠,如果lambda表达式返回true,跳出wait(),往下执行my_cond.wait(sbguard, [this]{if (!msgRecvQueue.empty())return true;return false;});command = msgRecvQueue.front();msgRecvQueue.pop_front();sbguard.unlock();//由于unique_lock的灵活性,可以提前解锁std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl; }}
private:std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令std::mutex mtx;//生成一个互斥量std::condition_variable my_cond;//生成一个条件变量对象};
int main()
{   A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();return 0;
}
//说明1:线程1执行完毕退出会导致线程2一直卡在那进行死等,这个是有缺陷的,但是这个缺陷很容易解决比如:规定一个command是退出命令,线程1在退出时插入这个退出命令并notify_one(),并线程二收到这个命令就退出循环。这样线程2就不会死等了。<解决死等方法很多,所以不是个事>
//说明2:如过第二个线程没有阻塞到wait(),线程1调用notify_one()其实是失效的,因为线程本身是唤醒的。<也可叫虚假唤醒>
//说明3:如果有如下两个线程,notify_one()一次只能唤醒其中的一个std::thread myOutnMsgObj1(&A::outMsgRecvQueue, &myobj); //这两个线程用的一个函数std::thread myOutnMsgObj2(&A::outMsgRecvQueue, &myobj);
//说明4:如果有如下两个线程,你想唤醒一次唤醒两个,可以用notify_all()std::thread myOutnMsgObj1(&A::outMsgRecvQueue, &myobj);std::thread myOutnMsgObj2(&A::outMsgRecvQueue, &myobj);

3.9、std::async()创建异步线程

<1>、使用std::async()创建异步线程/任务:

//std::async、std::future:创建后台任务并返回值
//希望线程返回一个结果
//std::async 是个函数模板,用来启动一个异步任务,启动后返回一个std::future对象,std::future是个类模板。
//启动异步任务:自动创建一个线程并开始执行对应的县城入口函数,它返回一个std::future对象,
//.............这个std::future对象里边就含有县城入口函数所返回的结果(线程返回的结果),我们可以通过调用future对象的成员函数get()来获取结果。
//.............std::future提供了一种访问异步操作结果的机制,即这个结果你不能马上拿到,但在将来可以拿到。
//*************************************************************************************//
示例一:普通函数做std::async()的参数
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
int mythread() {std::cout << "mythread() Start th_id = " <<std::this_thread::get_id()<< std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;
}
int main()
{   std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::future<int> result = std::async(mythread);//创建一个线程并开始执行(result与线程绑在了一起)std::cout << "continue...!" << std::endl;int def=0;std::cout <<"=" << result.get() << std::endl;//阻塞到这里,并等待mythread执行完成返回return 0;
}
//说明1:result.wait(),等待线程执行完毕,但并不能得到返回值
//说明2:result.get(),函数只能调用一次,多次调用会出异常
//*************************************************************************************//
示例二:类的成员函数做std::async()的形参
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
class A {
public:int mythread(int mypar) {std::cout << "=" << mypar << std::endl;std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;}
};
int main()
{   A myobj;int tmpar = 12;std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::future<int> result = std::async(&A::mythread,&myobj, tmpar);//创建一个线程并开始执行(result与线程绑在了一起)std::cout << "continue...!" << std::endl;int def=0;std::cout <<"=" << result.get() << std::endl;//阻塞到这里,并等待mythread执行完成返回return 0;
}
//说明1:如果你不调用result.get(),主线程依然会等待mythread执行完毕后再退出(why? 因为类析构)
//*************************************************************************************//
示例三、枚举std::lunnch类型的使用(延迟创建线程)
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
class A {
public:int mythread(int mypar) {std::cout << "=" << mypar << std::endl;std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;}
};
int main()
{   A myobj;int tmpar = 12;std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;//std::launch::deferred参数表示线程会被延迟创建,当你调用std::future的wait()护额get()时,线程才会创建并执行//如果你不调用wait()和get()时,线程不会执行//std::future<int> result = std::async(&A::mythread, &myobj, tmpar);//std::future<int> result = std::async(std::launch::async,&A::mythread,&myobj, tmpar); std::future<int> result = std::async(std::launch::deferred,&A::mythread,&myobj, tmpar); std::chrono::milliseconds dura(1000);std::this_thread::sleep_for(dura);std::cout << "continue...!" << std::endl;int def=0;std::cout <<"=" << result.get() << std::endl;//阻塞到这里,并等待mythread执行完成返回return 0;
}
//说明1:std::launch::deferred其实是没啥用的,当你使用它时,线程根本不创建,而且即使你调用了wait()和get(),也不会创建新线程,是在主线程中直接串行执行,不信的话可以打印线程ID查看。
//说明2:std::launch::async是缺省输入,如果第一个参数不设置,就默认使用这个参数。

<2>、std::async()和std::thread创建线程的区别

std::async()创建的线程,一般叫创建异步任务,有时候并不创建线程,比std::launch::deferred
        形参不仅会延迟执行,而且还不创建新的线程。重点是缺省时,创不创建新线程由系统的决定权由系统决定,这样系统在资源不够的情况下,不创建新线程(同步线程)。注意缺省时的默人参数是std::launch::asyn|std::launch::deferred,即创不创建新线程由系统决定。

std::thread创建线程时,如果系统的资源紧张,创建线程可能失败,造成系统崩溃的情况。

std::thread创建线程的返回值不好获取,没有std::async()创建的线程的返回值好获取。

一个程序中,线程数量不要超100-200个,因为不是线程越多,效率就越高,有一个极值点。

std::async()创建的任务到底是同步的还是异步的,可以通过std::future_status对象获取。

3.10、std::packaged_task<>的使用

//std::packaged_task:打包任务,把任务包装起来,方便将来作为线程入口函数。
//std::packaged_task是个类模板,他的模板参数是各种可调用对象。

MFC中的控件常常与一个对象绑定到一起,操作对象就可以更改控件的显示,QT中控件直接是一个对象,这些概念看似很普通,但都是围绕一个"万物皆对象的"思想,即面向对象编程,在C++线程中,线程函数等等也可以绑定到一个对象中(也叫封装到对象中),用操作对象的方式操作函数。

//示例1:把线程函数封装到std::packaged_task的对象中
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
//std::packaged_task:打包任务,把任务包装起来,方便将来作为线程入口函数。
//std::packaged_task是个类模板,他的模板参数是各种可调用对象。
int mythread(int mypar) {std::cout << "=" << mypar << std::endl;std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;
}
int main()
{   //把线程函数与一个<对象/变量>绑定,用操作<对象/变量>的方式使用线程函数,也算是符合万物皆是对象的概念std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::packaged_task<int(int)> mypt(mythread);//把线程函数包装到一个packaged_task对象中std::thread t1(std::ref(mypt),1);t1.join();std::future<int> result = mypt.get_future();std::cout << "result = " << result.get() << std::endl;return 0;
}
/***************************************************************************************/
//示例2:把lambda表达式封装到std::packaged_task的对象中
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
int main()
{   std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::packaged_task<int(int)> mypt([](int mypar) {std::cout << "=" << mypar << std::endl;std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;});std::thread t1(std::ref(mypt),1);t1.join();std::future<int> result = mypt.get_future();std::cout << "result = " << result.get() << std::endl;return 0;
}
/***************************************************************************************/
示例3:std::packaged_task的对象也可以直接被调用
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
int main()
{   std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::packaged_task<int(int)> mypt([](int mypar) {std::cout << "=" << mypar << std::endl;std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;});mypt(105); //std::packaged_task对象也可以直接被调用(相当于函数),不创建线程std::future<int> result = mypt.get_future();std::cout << "result = " << result.get() << std::endl;return 0;
}
/***************************************************************************************/
示例4:把std::packaged_task对象放入容器中
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
std::vector < std::packaged_task<int(int)> > mytasks; // 定义一个容器
int main()
{   std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::packaged_task<int(int)> mypt([](int mypar) {std::cout << "=" << mypar << std::endl;std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;});//把对象移动到容器mytasks中mytasks.push_back(std::move(mypt));//从容器中mytasks取出std::packaged_task<int(int)> mypt2;auto iter = mytasks.begin();mypt2 = std::move(*iter);mytasks.erase(iter);//删除iter指向的元素,此时iter失效,后续不可以再使用iter//执行mypt2(521);std::future<int> result = mypt2.get_future();std::cout << "result = " << result.get() << std::endl;return 0;
}
/***************************************************************************************/

3.11、std::promise<>的使用

一个线程等待获取另一个线程的计算结果:

#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
//std::promise,类模板,可实现在某个线程中给其赋值,在其他线程中读它。
void mythread(std::promise<int> &tmpp,int calc) {//做一系列复杂的操作calc++;calc *= 10;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);//计算出结果了int result = calc;//读取结果tmpp.set_value(result);//把结果保存到tmpp中return;
}
void mythread2(std::future<int>& tmpf,std::thread &th) {auto result = tmpf.get();std::cout << "result = " << result << ",id=" << std::this_thread::get_id() << std::endl;th.join();return;
}
int main()
{   std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;//线程1std::promise<int> myprom;std::thread th(mythread, std::ref(myprom),1);//创建线程1std::future<int> ful = myprom.get_future();//promise和future绑定,用于获取线程返回值//线程2std::thread th2(mythread2,std::ref(ful),std::ref(th));//创建线程2,在线程2中等待线程1的结果th2.join();return 0;
}

3.12、std::future<>的wait_for()方法

一个线程获取另一个线程的运算结果,必须等另一个线程执行完才能获取到,现实情况中,由于系统是非实时的,所以其每次执行完毕的所耗时间是不定的、浮动的,如果线程一直迟迟运行不完,我们也不能一直等待(等待是阻塞的),所以用wait_for()方法,每等待相应的时间就判断一下对方的状态,这样更灵活的等待对方的结果。也可以在对方是否超时做出相应处理。示例代码如下:

//示例:以std::async()为例
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
int mythread() {std::cout << "mythread() Start th_id = " << std::this_thread::get_id() << std::endl;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);std::cout << "mythread() Finish th_id = " << std::this_thread::get_id() << std::endl;return 5;
}
int main()
{std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::future<int> result = std::async(mythread);//创建一个线程并开始执行(result与线程绑在了一起)std::cout << "continue...!" << std::endl;int i = 0;while (true) {std::future_status status = result.wait_for(std::chrono::seconds(1));//周期1s唤醒自己,查看一次对方是否执行完毕std::cout << "第" <<++i<<"次唤醒:";if (status == std::future_status::ready) {std::cout << "线程执行完毕=>";std::cout << " result.get()= " << result.get() << std::endl;break;}else if (status == std::future_status::timeout) {std::cout << "线程未执行完毕" << std::endl;}else if (status == std::future_status::deferred) {std::cout << "线程被延迟执行" << std::endl;}}return 0;
}

3.13、std::shared_future<>的使用

在前面的历程中我们知道std::future<> 的get()方法只能被调用一次,因为其是一个移动语义,如果移动两次,肯定会出问题,这就限制了只能由一个线程中调用get()去获取某个线程的计算结果,为了解决这个限制(即多个线程都要获取某个线程的结果),引入了std::shared_future<>,代码如下:

//std::shared_future<>的get()方法使用的不是移动语义,而是复制语义,故可以get()两次
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
void mythread(std::promise<int>& tmpp, int calc) {//做一系列复杂的操作calc++;calc *= 10;std::chrono::milliseconds dura(5000);//延时5sstd::this_thread::sleep_for(dura);//计算出结果了int result = calc;//读取结果tmpp.set_value(result);//把结果保存到tmpp中return;
}
void mythread2(std::shared_future<int>& tmpf, std::thread& th) {auto result = tmpf.get();//get()只能被调用一次,因为其是个移动语义,所以不能移动第二次std::cout << "result = " << result << ",id=" << std::this_thread::get_id() << std::endl;//printf("result=%d,th_id=%d\r\n", result, std::this_thread::get_id());if (th.joinable()){th.join();}
}
void mythread3(std::shared_future<int>& tmpf, std::thread& th) {auto result = tmpf.get();//get()只能被调用一次,因为其是个移动语义,所以不能移动第二次std::cout << "result = " << result << ",id=" << std::this_thread::get_id() << std::endl;//printf("result=%d,th_id=%d\r\n", result, std::this_thread::get_id());if (th.joinable()) { th.join(); }
}
int main()
{std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;//线程1std::promise<int> myprom;std::thread th(mythread, std::ref(myprom), 1);//创建线程1std::shared_future<int> sful = myprom.get_future();//promise和future绑定,用于获取线程返回值//线程2、3std::thread th2(mythread2, std::ref(sful), std::ref(th));//创建线程2,在线程2中等待线程1的结果std::thread th3(mythread3, std::ref(sful), std::ref(th));//创建线程3,在线程3中等待线程1的结果//join线程2、3th2.join(); th3.join();return 0;
}
//说明1:std::future<int>也可以转为std::shared_future<int>,示例如下
//       std::future<int> ful = myprom.get_future();
//       std::shared_future<int> sfulx(ful.share());//ful.share()也是移动语义
//说明2:std::future<int>和std::shared_future<int>的valid()方法可以判断其对象里的值是否有效
//       例如:if(sful.valid()){  }//判断sful变量是否被std::move()移动过

3.14、std::atomic<>创建原子对象

//示例1:整形原子操作
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
//原子操作:std::atomic(类模板)、不可分割的操作 <针对简单的操作>
//互斥量:多线程中,保护共享数据:锁=>保护共享数据=>开锁 <针对复杂的操作>
/** 原子1:使用原子汇编码,代码粒度小于中断的的代码。* 原子2:使用开关中断,暂时屏蔽中断从而不让系统调度,就可以实现原子性。* 以上是两种原子性。这样的代码的执行叫原子操作,它是一种无锁技术,效率上比互斥量好。* 但原子操作是很简单的,能力一般。而互斥量可以互斥整个代码段,所以各有优缺点。 */
std::atomic<int> g_mycount = 0; //定义一个int原子对象
void mythread() {for ( int i = 0;i < 1000000;i++){g_mycount++; //原子操作//g_mycount+=1; //原子操作//g_mycount=g_mycount+1;//不是原子操作//结论:不是所有的操作符都是原子性的,一般针对++、--、+=、&=、|=、^=是原子性的
}
int main()
{std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::thread th1(mythread);//创建线程1std::thread th2(mythread);//创建线程2th1.join(); th2.join();std::cout << "g_mycount=" << g_mycount << std::endl;return 0;
}
//*************************************************************************************//
//示例2:布尔型原子操作
#include <iostream>
#include <list>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
std::atomic<bool> g_ifend = true;//定义一个bool原子对象
void mythread() {std::chrono::milliseconds dura(1000);while (g_ifend) {//原子操作std::cout << "thread_id = " << std::this_thread::get_id() << "运行中" << std::endl;std::this_thread::sleep_for(dura);}std::cout << "thread_id = " << std::this_thread::get_id() << "运行结束" << std::endl;
}int main()
{std::cout << "main() Start th_id = " << std::this_thread::get_id() << std::endl;std::thread th1(mythread);//创建线程1std::thread th2(mythread);//创建线程2std::chrono::milliseconds dura(5000);//主线程休息5sstd::this_thread::sleep_for(dura);g_ifend=false;//原子操作、让线程退出循环th1.join(); th2.join();std::cout << "主线程结束"<< std::endl;return 0;
}
//说明1:原子操作项目中一般不常用。
//说明2:一般用于计数,比如数据包的统计。
//说明3:代码一定要稳定,给公司写项目时一定要用自己拿得准的的代码(稳定首位)。
//*************************************************************************************//

3.15、Windows临界区的简单使用

临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
互斥量:为协调共同对一个共享资源的单独访问而设计的。
信号量:为控制一个具有有限数量用户资源而设计。
事    件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

/***************************************************************************************/
//示例代码1:临界区的简单使用
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>#include <windows.h>//不属于C++库,此库很大,一般使用预编译
#define __WINDOWSJQ_
//windows临界区
class A {
public://线程1:接受到玩家命令,插入到一个队列中(写)void inMsgRecvQueue() {for (int i = 0; i < 10000; ++i) {std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;#ifdef __WINDOWSJQ_ EnterCriticalSection(&my_winsec);//进入临界区msgRecvQueue.push_back(i); //i就是收到的命令LeaveCriticalSection(&my_winsec);//离开临界区#elsemtx.lock();msgRecvQueue.push_back(i); //i就是收到的命令mtx.unlock();#endif}}bool outMsgLULProc(int& command) {#ifdef __WINDOWSJQ_ EnterCriticalSection(&my_winsec);//进入临界区if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();msgRecvQueue.pop_front();LeaveCriticalSection(&my_winsec);//离开临界区return true;}LeaveCriticalSection(&my_winsec);//离开临界区#elsemtx.lock();if (!msgRecvQueue.empty()) {command = msgRecvQueue.front();msgRecvQueue.pop_front();mtx.unlock();return true;}mtx.unlock();#endifreturn false;}//线程2:把数据从消息列表中取出的线程(读,删)void outMsgRecvQueue() {int command = 0;for (int i = 0; i < 10000; ++i) {bool ret = outMsgLULProc(command);if (ret == true) {//链表非空->取元素成功std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;}else {//链表空->取元素失败std::cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << std::endl;}}}//构造函数A() {#ifdef __WINDOWSJQ_ InitializeCriticalSection(&my_winsec);//初始化临界区#endif}
private:std::mutex mtx;std::list<int> msgRecvQueue;//链表(消息队列),专门用于代表玩家发来的命令//------创建windows临界区#ifdef __WINDOWSJQ_ //windows中的临界区,非常类似于C++11中的mutexCRITICAL_SECTION my_winsec;//临界区必须初始化,在构造函数中初始化#endif
};
int main()
{std::cout << "The main thread start and thread_id = " << std::this_thread::get_id() << "\r\n";//--------------------------------//A myobj;std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobj);std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);myOutnMsgObj.join();myInMsgObj.join();//--------------------------------//std::cout << "The main thread end and thread_id = " << std::this_thread::get_id() << "\r\n";return 0;
}
/***************************************************************************************/
//示例代码2:临界区的的多次进入
//windows的同一个线程中,相同的临界区变量,可以进入多次,但也离开相应的次数
{EnterCriticalSection(&my_winsec);//进入临界区EnterCriticalSection(&my_winsec);//进入临界区//执行相应代码......LeaveCriticalSection(&my_winsec);//离开临界区LeaveCriticalSection(&my_winsec);//离开临界区
}
//说明1:C++11的互斥量中,相同的变量在同一个线程中不可以锁两次
//说明2:像智能指针,区域锁等这种自动释放的类,叫做RAII类,资源获取即初始化,其主要实现机理
//.......就是在构造中初始化资源,在析构中释放资源。
/***************************************************************************************/

四、线程类的封装模板参考

4.1、线程封装前言

更新中。。。

五、线程池类的封装模板参考

5.1、线程池封装前言

服务器-客户端程序,每来一个客户端,服务器端创建一个线程提供服务。
客户端少没有问题,如果一下子来上千、上万个客户端,创建线程就可能失败。
程序不稳定,编写的代码中,偶尔创建一个线程这种代码出现问题,让人不安。
为了应对上述不稳定的问题,提出了线程池的概念
<1>、线程池的概念:
        把一堆线程弄到一起,统一管理。这种统一管理调度,循环利用线程的方式叫线程池。
<2>、实现方式:
        在程序启动时,一次性的创建好一定数量的线程,要使用线程时,从线程池中抓过来一个进行使用,使用完毕后再放回到线程池中,程序运行过程中不再临时创建线程,使程序更稳定。
<3>、线程数量:
        采用某些技术开发程序,api接口供应商有时会给出数量的建议,按照指示设置数量。

更新中。。。

39、C++11多线程及其学习笔记相关推荐

  1. Linux与C++11多线程编程(学习笔记)

    多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...

  2. 多线程编程学习笔记——线程池(二)

    接上文 多线程编程学习笔记--线程池(一) 三.线程池与并行度 此示例是学习如何应用线程池实现大量的操作,及与创建大量线程进行工作的区别. 1. 代码如下 using System; using Sy ...

  3. (实验39)单片机,STM32F4学习笔记,代码讲解【FATFS实验】【正点原子】【原创】

    文章目录 其它文章链接,独家吐血整理 实验现象 主程序 FATFS初始化程序 代码讲解 其它文章链接,独家吐血整理 (实验3)单片机,STM32F4学习笔记,代码讲解[按键输入实验][正点原子][原创 ...

  4. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记--async和await(一) 接上文 多线程编程学习笔记--async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  5. 多线程编程学习笔记——任务并行库(二)

    接上文 多线程编程学习笔记--任务并行库(一) 三.   组合任务 本示例是学习如何设置相互依赖的任务.我们学习如何创建一个任务的子任务,这个子任务必须在父任务执行结束之后,再执行. 1,示例代码如下 ...

  6. 多线程编程学习笔记——任务并行库(三)

    接上文 多线程编程学习笔记--任务并行库(一) 接上文 多线程编程学习笔记--任务并行库(二) 六.   实现取消选项 本示例学习如何实现基于Task的异步操作进行取消流程,以及在任务真正运行前如何知 ...

  7. 多线程编程学习笔记——使用并发集合(三)

    接上文 多线程编程学习笔记--使用并发集合(一) 接上文 多线程编程学习笔记--使用并发集合(二) 四.   使用ConcurrentBag创建一个可扩展的爬虫 本示例在多个独立的即可生产任务又可消费 ...

  8. HALCON 21.11:深度学习笔记---语义分割/边缘提取(12)

    HALCON 21.11:深度学习笔记---语义分割/边缘提取(12) HALCON 21.11.0.0中,实现了深度学习方法. 本章介绍了如何使用基于深度学习的语义分割,包括训练和推理阶段. 通过语 ...

  9. HALCON 21.11:深度学习笔记---对象检测, 实例分割(11)

    HALCON 21.11:深度学习笔记---对象检测, 实例分割(11) HALCON 21.11.0.0中,实现了深度学习方法. 本章介绍了如何使用基于深度学习的对象检测. 通过对象检测,我们希望在 ...

  10. HALCON 21.11:深度学习笔记---分类(10)

    HALCON 21.11:深度学习笔记---分类(10) HALCON 21.11.0.0中,实现了深度学习方法. 本章介绍了如何在训练和推理阶段使用基于深度学习的分类. 基于深度学习的分类是一种对一 ...

最新文章

  1. java B2B2C源码电子商务平台-基于Consul的分布式锁实现
  2. centos 安装2个mysql_CentOs服务器下安装两个个MySql数据库踩坑日记
  3. Jewels and Stones
  4. python换成中文版_在python中如何将“\”替换为“/”?
  5. 解决方法|ESP8266环境搭建出现 usrbinenv bashr :没有那个文件或目录
  6. 史上最全的MSSQL复习笔记
  7. 立即修复!微软史上最严重漏洞之一 Netlogon 细节被公开,三秒接管企业网络
  8. 基于netty搭建websocket,实现消息的主动推送
  9. MySQL索引类型详解,让MySQL高效运行起来
  10. Could not find artifact com.taotao:taotao-parent:pom原因
  11. FwmarkServer 实现以及功能分析
  12. android 银行接口,iOS/Android银行卡识别sdk/开发包/api/接口
  13. uniapp开发原生android插件,获取浏览器cookie
  14. 数据库的基本操作和约束
  15. Drools(2):Drools快速入门
  16. 肠道微生物群在冠心病中的作用
  17. java图像处理-(指定区域内)灰度化、透明化(alpha通道)处理
  18. Const用法总结:Const,Const函数,Const变量,函数后面的Const (转)
  19. 安逸云中小云厂商机遇
  20. Saiku设置展示table数据不隐藏空的行数据信息(二十六)

热门文章

  1. windows或linux下用Virtualbox安装Win 8.1等系统错误0x000000C4解决办法
  2. STC15单片机内部RAM讲解
  3. [转载]揭秘骇人的湘西“赶尸”奇俗
  4. 关于阻抗设计的建议-来至深南电路板厂的心水总结
  5. springboot对接支付宝支付接口(详细开发步骤总结)
  6. Android中可展开的列表组件(ExpandableListView)的使用
  7. 电脑无法启动显示计算机comt,电脑开机显示press any key to restart进不了系统怎么办?...
  8. 面向对象 重写和重载
  9. 成都盛铭轩:做好主图要从这些方面做
  10. win10 红警启动必要文件