一、什么是并发
1、并发的概念:两个或更多的任务同事进行,一个程序同时进行多个独立的任务。
2、实现的方法:CPU由操作系统调度,进行任务切换(上下文切换)。
3、引入原因:提高性能
4、线程不是越多越好,存在空间、切换效率上的一个平衡。
5、并发的实现方式:多进程并发、单进程多线程并发。
6、进程间的通讯方式:管道、文件、消息队列、共享内存、socket通讯。
二、进程与线程的概念
1、进程:运行起来的可执行程序。
2、线程:每个进程都有唯一的主线程,主线程与进程唇齿相依。线程是代码执行的基本单元和通路,可以通过另外创建线程在同一时刻执行其他的代码或任务。
三、线程的使用
1、线程的开始与结束
主线程从main函数开始执行,主线程执行完,整个进程执行完毕。线程可以从一个函数开始执行,若主线程执行完毕,其他的子线程也会被终止。
2、线程的启动方式
(1)以函数名为参数
void myprint()
{
}
int main()
{
std::thread mytobj(myprint);
mytobj.join();
return 0;
}
myprint是一个可调用对象。
注意:一个良好的程序,主线程要等待子线程执行完,正常退出。
join会阻塞主线程,等待子线程执行完毕,然后主子线程回合。
detach会分离主子线程,主线程不与子线程汇合,detach后,线程对象将与主线程失联,驻留在后台运行(被C++运行时库接管),可能导致主线程已经退出了,子线程还没执行完的问题。
joinable:在join之前,可以通过这个接口判断线程能否join。
(2)类对象(重载了括号运算符的类的对象)
class Ta
{
public:
void operator()(){}
Ta(int& i){}
}
int main()
{
int myi = 10;
Ta ta(myi);
std::thread mytobj(ta);
}
说明:参数传递的问题在下面会讲述。ta这个对象实质上是被复制到线程中
(3)lambda表达式
auto mylambda = []{
....
};
std::thread mytobj(mylambda);
mytobj.join();
3、线程函数传递参数
(1)传递临时对象
参数传递引用,实际上都是值传递的方式
参数传递指针,未进行拷贝,在detach的方式下,存在隐患
实际上,对于简单的内置类型,应该传值。传递类的对象,为了避免隐式转换的问题,应用临时对象,同时线程函数用const引用来接。
void myprint(int i, const string& s)
{
}
int main()
{
int myi = 0;
string s("hello world");
std::thread mytobj(myprint,myi,string(s));
mytobj.join();
}
说明:只要用临时对象,就能保证参数在主线程中准备完毕(拷贝),然后要求线程函数用const引用来接,否则会两个拷贝。同时编译器在这种情况下强制线程函数的参数类型为const引用。
(2)真正传递引用
为了避免上诉的临时对象的拷贝,可以使用std::ref(临时对象)的形式来传递实参,这种情况下属于真正的传递引用,而函数的参数可不加const。此时,线程只能用join的方式汇合。
(3)传递智能指针
std::unique_ptr p(new int(10));
std::thread tobj(std::move(p);
tobj.join();
std::move的用法详情可看C++的智能指针相关的内容。
(4)用类成员函数作为线程的入口函数
std::thread tobj(&A::func,mya);
这种形式同样为对象的拷贝,需要传递引用的话,可以采取下面的方式
ref(mya)
线程参数总结:如果实参不能进行拷贝,就别用detach的方式了,可能因为主线程执行完毕了,内存被回收了,子线程还继续访问导致的错误问题。
4、获取当前线程的id
std::this_thread::get_id()