项目中用到了线程池,在此正式的总结一下,线程池说到底还是多线程,所以,将多线程的知识梳理一下。
基础
C++11中新增了线程库,用来支持多线程的开发。
相关头文件<condition_variable>
编译来说:
windows下visual studio2017已经支持
linux下gcc 4.8.1/g++ -std=c++11 xxx.cpp -lpthread
what is thread?
其实每个c++应用程序中都有一个默认的主线程,就是main函数。主线程用来规规矩矩的做自己的事情。
比如说我要做馒头。主线程就是一步一步来。烧火,和面,做馒头,煮水,蒸馒头。这其实说起来就是一个进程。但是,我们想早点吃上馒头怎么办?很简单,让人帮忙烧火煮水,你来和面做馒头。因为两个流程并不是相互影响的。这就是thread。
#include <iostream>
#include <thread>
#include <ctime>
using namespace std;
void shaohuozhushui()
{
int i = 1;
while (i < 1000)
{
cout << "I am a thread,I am shuishenhuoreing." << endl;
i++;
}
}
void zuomantou(int n)
{
int j = 1;
while (j < 1000)
{
cout << "T am also a thread,I will make " << n << " ge mantou." << endl;
j++;
}
}
int main()
{
clock_t start, end;
start = clock();
cout << "make mantou begin!" << endl;
//shaohuozhushui();
//zuomantou(5);
thread th1(shaohuozhushui);
thread th2(zuomantou, 5);
th1.join();
th2.join();
cout << "done!" << endl;
end = clock();
cout << "The runtime is " << (double)(end - start) / CLOCKS_PER_SEC << "s";
return 0;
}
上面这个例子,我们让一个线程烧火煮水,一个做馒头。就是多线程。
注释里面的就是不用多线程,我们可以加个时间看一下,由于电脑环境不同,所以每次运行时间也不太相同。我简单截取两个对比下。
前面的是主线程的,后面的是多线程的,由于例子过于简单,貌似没什么区别。但可以自己做一个看一看。过程中的结果太长所以我没有截取。做的过程就能看出来差异。
thread的创建,std::thread obj(callback)。新线程在创建后立即开始,里面可以附加一个回调,当线程启动时被执行。
回调函数的附加方法,可以看我上面的例子就是两种方法。
thread obj(xxx);
如果回调函数需要参数,则需要将参数也传递进去。thread obj(xxx,1,2);
当然,也可以通过类中的函数创建对象。例子见下。
#include <iostream>
#include <thread>
class MYClass {
public:
MYClass() { }
MYClass(const MYClass& obj) { }
void function(int x) {
std::cout << "I AM A THREAD IN MYCLASS " << x << std::endl;
}
};
int main() {
MYClass Obj;
int x = 6;
std::thread threadObj(&MYClass::function, &Obj, x);
threadObj.join();
return 0;
}
同时,每一个thread对象都有自己的id,可以获取到。
thread obj;
obj.get_id();//获得对象线程的ID
this_thread::get_id();//获得此线程的ID
例子如下:
#include <iostream>
#include <thread>
using namespace std;
void ID()
{
cout << "I am a thread, my ID is " << this_thread::get_id() << endl;
}
int main() {
thread th1(ID);
thread th2(ID);
if (th1.get_id() != th2.get_id())
{
cout << "th1's ID is" << th1.get_id() << endl;
cout << "th2's ID is" << th2.get_id() << endl;
}
th1.join();
th2.join();
return 0;
}
结果如下,当然,大家的运行结果可能和这个不一样,我这也是为了演示作用,运行了好几次才捕捉到了这个输出。这是之后说的异步问题。
join和detach
我们在创建了一个线程之后就要回收这个线程,不能说让他一直烧火,烧的什么都没有了,让他一直做馒头,永远也吃不完了。
这就是join的作用,一个线程在创建之后,便可以通过此对象的join函数等待这个线程执行完毕回收。
在第一个例子里,主线程在两个线程join完毕之后,才继续执行之后的代码。
detach的不同之处在于,我创建了一个线程对象后,可以将他detach,执行操作后,此线程就与实际执行的线程脱离了关系,也可以这么理解,蒲公英的孩子可以到处乱飞,就是detach了。他执行完毕后会回收该线程资源。
但detach可能会有风险,那就是如果线程要操作某对象,但是此时主线程已经结束,析构了此对象时,就会出现异常。
#include <iostream>
#include <thread>
class MYClass {
public:
MYClass() { }
MYClass(const MYClass& obj) { }
void function(int x) {
std::cout << "I AM A THREAD IN MYCLASS " << x << std::endl;
}
};
int main() {
MYClass Obj;
int x = 6;
std::thread threadObj(&MYClass::function, &Obj, x);
threadObj.detach();
std::thread threadObj1(&MYClass::function, &Obj, x);
threadObj1.detach();
std::cout << "done" << std::endl;
return 0;
}
原本应该是两个线程调用了Obj的成员函数,进行两次打印。但是结果如下。
所以detach会有异常发生。
另外,join和detach都不能对一个对象重复使用。对一个对象join两次是会出错的。因此可以先判断joinable();