线程
概述
对于早期的C++语言而言,如果想使用线程,我们需要根据不同的平台使用不用的接口,比如:在Linux平台上,我们需要借助POSIX标准的线程库,在windows上需要借助windows线程库,因为C++自己没有独立的线程库。为了解决这个问题,在C++11标准中,做了完善,C++自己引入了与平台无关的线程库,这个库是语言层面的库,这就是C++11线程库,接下来我们就来学习一下C++11线程库的知识点。
头文件
#include<thread>
函数接口
在C++11中,将线程封装成了类的概念,下面就是线程类的构造函数形式。
thread() noexcept; //(1)
thread( thread&& other ) noexcept; //(2)
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args ); //(3)
thread(const thread&) = delete;//(4)
从上面的几种形式可以看出,第一种形式,可以创建一个空的线程对象,但是线程创建出来之后,需要做任务,单独使用这种形式没有意义;第二种形式,可以从另外一个线程对象转移过来;第三种形式,传递任何可调用对象的形式,这种形式使用的最为通用;第四种形式,表明线程对象不能进行复制。
线程启动
线程的启动,也就是线程入口函数的传递方式。虽然函数接口中有多种形式,可以创建无参对象(线程没有入口函数,也就是线程不做任务),但是这种没有什么意义,一般不使用;可以将两外一个线程对象移动过来,但是移动之后,另外一个线程对象就没有了,这种一般用的也不多,接下来我们重点研究第三种接口形式。
传递普通函数
void func() {
cout << "void func()" << endl;
}
void test() {
thread th1(func);
}
传递函数指针(引用)
void func() {
cout << "void func()" << endl;
}
void test() {
void (*pFunc)() = func;
thread th1(pFunc);
}
void test2() {
void (&rFunc)() = func;
thread th1(rFunc);
}
传递函数对象
class Example {
public:
void operator()(int x) {
cout << "void operator()()" << endl;
cout << "x = " << x << endl;
}
};
void test() {
Example ex;
thread th1(ex, 10);
}
传递lambda表达式
void test() {
int a = 10;
thread th1([&a](){
a = 100;
cout << "a = " << a << endl;
})
};
传递function对象
void func() {
cout << "void func()" << endl;
}
void test() {
function<void()> f = bind(&func);
thread th1(f);
}
线程终止
我们使用std::thread创建的线程对象是进程中的子线程,一般进程中还有主线程,在程序中就是main线程,那么当我们创建线程后至少是有两个线程的,那么两个线程谁先执行完毕谁后执行完毕,这是随机的,但是当进程执行结束之后,主线程与子线程都会执行完毕,进程会回收线程拥有的资源。并且,主线程main执行完毕,其实整个进程也就执行完毕了。一般我们有两种方式让子线程结束,一种是主线程等待子线程执行完毕,我们使用join函数,让主线程回收子线程的资源;另外一种是子线程与主线程分离,我们使用detach函数,此时子线程驻留在后台运行,这个子线程就相当于被C++运行时库接管,子线程执行完毕后,由运行时库负责清理该线程相关的资源。使用detach之后,表明就失去了对子线程的控制。
join函数
void func() {
cout << "void func()" << endl;
cout << "I'm child thread" << endl;
}
void test() {
cout << "I'm main thread" << endl;
thread th1(func);
th1.join();//主线程等待子线程
}
这种等待子线程结束的方式是最常见的,也是最容易理解的,类似POSIX标准线程库中的pthread_join函数。
detach函数(了解)
void threadFunc() {
cout << "I'm child thread 1" << endl;
cout << "I'm child thread 2" << endl;
cout << "I'm child thread 3" << endl;
cout << "I'm child thread 4" << endl;
cout << "I'm child thread 5" << endl;
}
int main(void) {
thread th1(threadFunc);
th1.detach();
cout << "I'm main thread 1" << endl;
cout << "I'm main thread 2" << endl;
cout << "I'm main thread 3" << endl;
cout << "I'm main thread 4" << endl;
cout << "I'm main thread 5" << endl;
return 0;
}
执行上述代码,会发现并不是执行完子线程,再接着执行完主线程后面的代码,如果将detach()函数换成join()函数,就是先执行完子线程,然后执行子线程。
使用deatch可以让主线程与子线程分离,主线程不必回收子线程,但是有可能主线程在子线程之前结束,如果子线程使用了主线程中的局部变量,那么就会出现很多问题。
传递局部变量(对象)
void threadFunc(const int &value, char *pstr) {
cout << "I'm child thread" << endl;
printf("&value = %p\n", &value);
//value的地址与num的地址不一致
printf("pstr = %p\n", pstr);
//与str指向变量的地址一样,如果主线程先执行完,pstr指向为空
}
int main(void) {
//传递局部(变量)对象作为参数详解
int num = 10;
char str[] = "hello";
printf("&num = %p\n", &num);
printf("str = %p\n", str);
thread th1(threadFunc, num, str);
//num与str使用的值传递
// th1.join();
th1.detach();
cout << "I'm main thread" << endl;
return 0;
}
隐式类型转换
void threadFunc(int value, const string &s) {
cout << "I'm child thread" << endl;
printf("&value = %p\n", &value);
printf("s.c_str() = %p\n", s.c_str());
printf("s.c_str() = %s\n", s.c_str());
}
int main(void) {
int num = 10;
char str[] = "hello";
printf("&num = %p\n", &num);
printf("str = %p\n", str);
thread th1(threadFunc, num, str);
// str会隐式转换为string(str)
th1.detach();
cout << "I'm main thread" << endl;
return 0;
}
疑问:在(1)处,因为str的类型与threadFunc中s的类型不匹配,那么就需要从str隐式转换为string(str),但是有没有这种情况呢,这个隐式转换的时机比较晚,在main执行完成后,str都已经回收了,才进行了隐式转换。那这样不就是有问题的吗?可以思考一下怎么处理这个问题呢?
解决方案:将(1)处的str隐式转换的过程显示写出来即可。
threadth1(threadFunc,num,string(str));
所以,可见使用detach函数会有很多坑,如果没有必要,可以直接使用join函数即可。
线程的状态
线程类中有一成员函数joinable,可以用来检查线程的状态。如果该函数为true,表示可以使用join()或者detach()函数来管理线程生命周期。
void test() {
thread t([] {
cout << "Hello, world!" << endl;
});
if (t.joinable()) {
t.detach();
}
}
void test2() {
thread th1([] {
cout << "Hello, world!" << endl;
});
if (t.joinable()) {
t.join();
}
}
线程id
为了唯一标识每个线程,可以给每个线程一个id,类型为 std::thread::id
,可以使用成员函数get_id()进行获取。
void test() {
thread th1([]() {
cout << "子线程ID:" << std::this_thread::get_id() << endl;
});
th1.join();
}