线程是处理器任务调度和执行的基本单位。在C++ 11之前,只能调用系统API创建线程:
C++ 11开始,提供了线程类,叫做std::thread。它是语言层面的thread,因此使用新标准提供的线程库编写的程序是可以跨平台的,有利于多线程程序的移值。
可以简单通过使用thread库,来管理多线程,在使用std::thread前,要先需要引用头文件:
#include
前面讲到,std::thread是一个class,有如下几个方法构造一个thread对象:
thread() noexcept; | 默认构造函数,创建一个空的 std::thread 执行对象,底层线程并没有真正被创建。即这个thread对象还未关联任何执行线程 |
template |
初始化构造函数,可以传入任意函数对象,以及函数参数,包括 lambda表达式(带变量捕获或者不带),函数(全局函数或者成员函数),函数对象 。在创建thread对象时,std::thread构建函数中的所有参数均会按值并以副本的形式保存成一个tuple对象 |
thread(thread&& _Other) noexcept; | move构造函数,调用成功之后该线程对象会转移 |
thread(const thread&) = delete; | 拷贝构造函数 ,被禁用 |
thread& operator=(const thread&) = delete; | 拷贝赋值运算,被禁用 |
从thread的构造函数可以看出,thread类的对象能够移动,但是不能复制,所以线程的归属权可以在thread对象间转移,这样保证了对于任一特定的执行线程,任何时候都只有唯一的thread对象与之关联。
下面是一个最简单的创建一个thread的例子:
#include
#include
using namespace std;
void funcA() {
cout << "funcA is runing\n";
}
int main() {
thread th(funcA);
th.join();
return 0;
}
这里在主线程(即main线程)里创建了一个子线程,子线程的入口函数是funcA();当线程执行完入口函数后,线程也会退出。
如果需要将某个类的成员函数作为线程函数,我们则应该传入一个函数指针,指向该成员函数,并且还要给出一个对象指针,作为该函数的第一个参数。
比如如下这个程序,对象指针由a的地址充当,新线程会调用a.do_some_work()。如果成员函数还需要有其他参数传入,则再向thread的构造函数中继续添加参数
#include
#include
using namespace std;
class A {
public:
A() { val = 0; }
A(int i):val(i) {}
void do_some_work() {
//do something
cout << "val = " << val << endl;
}
private:
int val;
};
int main() {
A a(100);
thread th(&A::do_some_work,&a);
th.join();
return 0;
}
thread的成员函数中首先要讲的就是join()和detach()了。
一旦开启了一个线程,就需要明确的决定是等待这个线程结束(join),还是让它在后台独自运行(detach)。假如到了std::thread对象销毁的时候还没有决定好,即一个线程对象在析构的时候仍然与某个线程关联,那么系统将会调用std::terminate()抛出异常(std::thread的析构函数调用的std::terminate())终止整个程序。
下面举个例子,演示一下join()和detach()的区别:funcA里会休眠1s,如果采用join()的方式,从运行结果可以看到,main线程会等到funcA()执行完后,才会继续往下执行,即主线程被子线程阻塞了。
#include
#include
using namespace std;
void funcA() {
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "funcA is runing. \n";
}
int main() {
thread th(funcA);
th.join();
cout << "this main thread. \n";
system("pause");
return 0;
}
如果采用detach()的方式,从运行结果可以看到,main线程不会等funcA()执行完,就会继续往下执行,主线程不会被子线程阻塞。调用detach后,没有直接的方法与之通信,也不能再join等待这个线程结束了。detach分离的线程,其所有权和控制权被转交给了C++运行时库,以确保与线程相关的资源能被正确回收。
#include
#include
using namespace std;
void funcA() {
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "funcA is runing. \n";
}
int main() {
thread th(funcA);
th.detach();
cout << "this main thread. \n";
system("pause");
return 0;
}
注意:我们只需要在std::thread对象销毁之前做出上述join还是detach决定就可以,并不一定是说在线程对像启动线程后,就立马要做出决定。但是线程本身在join或者detach前可能就已经结束了
一个thread对象只可能处于可联结或不可联结这两种状态中的一种,joinable()这个函数可以判断thread对象当前是否处于联结状态。
① 可联结:
std::thread对象(对象由父线程所有)与底层线程保持着关联时,即为joinable状态。 当线程处于己运行或可运行,或处于阻塞时是可联结的。注意:如果某个底层线程已经执行完任务,但是没有调用其join()的话,那它仍然还处于joinable状态。
② 不可联结:
a. 不带参构造的std::thread对象为不可联结,因为底层线程还没创建。
b. 己移动的std::thread对象为不可联结。
c. 己调用join或detach的对象为不可联结状态。因为调用join()以后,底层线程就己经结束了;调用detach()则会把std::thread对象和对应的底层线程之间的连接断开。
下面简单举一个例子:在主线程先创建一个thread,然后主线程自己休眠1s后,再调用子线程的join(),看一下尽管子线程已经很快执行完毕的情况下,主线程调用子线程对象的join()的前后的联结状态~
#include
#include
using namespace std;
void funcA() {
cout << "funcA is runing. \n";
}
int main() {
thread th(funcA);
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << " 1. Befor call th.join(), th isJoinable: "<
从运行结果看到,尽管新的thread已经执行完,但是主线程里还没有调用join()之前,这个新的thread对象还是处于可联结的状态;在调用join()以后,底层线程己经结束,所以thread对象就处于不可联结的状态了
get_id() | 获取线程的Id |
void swap (thread& x) | 交换两个线程对象,将对象的状态与x的状态交换。 |
native_handle() | 获取本机句柄类型 |
hardware_concurrency | 系统支持的最大线程数量,当系统无法获取时,函数返回0 |
有时候我们需要在线程执行代码里面对当前调用者线程进行操作,针对这种情况,C++11里面专门定义了一个命名空间this_thread。在#include
std::this_thread::get_id() | 获取当前的线程 id,返回的类型是 std::thread::id |
std::this_thread::sleep_for() | 当前线程休眠指定的时间 |
std::this_thread::sleep_until() | 当前线程休眠直到指定时间点 |
std::this_thread::yield() | 当前线程让出CPU,允许其他线程运行 |
此外,this_thread
还包含重载运算符==
和!=
,用于比较两个线程是否相等。