C++ std::thread

1. 引言

线程是处理器任务调度和执行的基本单位。在C++ 11之前,只能调用系统API创建线程:

  • Windows有提供用于多线程创建和管理的win32 api;
  • Linux有POSIX(可移植操作系统接口(Portable Operating System Interface) )多线程标准,即pthread

C++ 11开始,提供了线程类,叫做std::thread。它是语言层面的thread,因此使用新标准提供的线程库编写的程序是可以跨平台的,有利于多线程程序的移值。

可以简单通过使用thread库,来管理多线程,在使用std::thread前,要先需要引用头文件:

#include 

2. thread的基本使用

 创建线程

前面讲到,std::thread是一个class,有如下几个方法构造一个thread对象:

thread() noexcept;  默认构造函数,创建一个空的 std::thread 执行对象,底层线程并没有真正被创建。即这个thread对象还未关联任何执行线程
template explicit thread(_Fn&& _Fx, _Args&&... _Ax) 初始化构造函数,可以传入任意函数对象,以及函数参数,包括

 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;
}

std::thread 常用的成员函数

thread的成员函数中首先要讲的就是join()和detach()了。

1.  join() & detach() & joinable()

        一旦开启了一个线程,就需要明确的决定是等待这个线程结束(join),还是让它在后台独自运行(detach)。假如到了std::thread对象销毁的时候还没有决定好,即一个线程对象在析构的时候仍然与某个线程关联,那么系统将会调用std::terminate()抛出异常(std::thread的析构函数调用的std::terminate())终止整个程序。

  •  join():阻塞当前线程,等待子线程执行完毕。即主线程等待子线程结束后才可执行下一步(串行)
  • detach(): 分离子线程,从调用线程中分离出新的线程,之后不能再与新线程交互,新的线程成为后台线程,被C++运行时库接管,不会阻塞当前主线程,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。多说一下,如果选用了detach,即分离这种方式的话,如果分离的时候新线程还未结束运行,那它将继续运行,甚至在thread对象销毁很久之后依然运行,他只有最终从线程函数返回时才会结束运行

      下面举个例子,演示一下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前可能就已经结束了

  • joinable()

一个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对象就处于不可联结的状态了

2. std::thread 其他常用的成员函数

get_id() 获取线程的Id
void swap (thread& x)  交换两个线程对象,将对象的状态与x的状态交换。
native_handle() 获取本机句柄类型
hardware_concurrency 系统支持的最大线程数量,当系统无法获取时,函数返回0

std::this_thread命名空间

有时候我们需要在线程执行代码里面对当前调用者线程进行操作,针对这种情况,C++11里面专门定义了一个命名空间this_thread。在#include 头文件后,除了可以使用前面所说的std:thread线程类外,还可以使用std::this_thread命名空间,在this_thread里提供了一些关于当前线程的功能函数。具体如下:

 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还包含重载运算符==!=,用于比较两个线程是否相等。

你可能感兴趣的:(c++,开发语言)