C++11多线程:线程的创建及启动

文章目录

  • 启动线程
    • 传递函数对象为参数
    • 传递成员函数为参数
    • 传递全局函数为参数
    • 传递lambda函数为参数
    • 也可调用std::thread的无参构造
  • join()、joinable()、detach() 等函数
    • Join函数
    • detach 函数
    • joinable函数
  • Join函数到底干了什么?
  • 必须join或者detach吗?
  • 线程资源不能被覆盖

在C++11之前的C++98/03标准是不支持的多线程的。想要使用多线程需要使用使用POSIX C 或者其他API功能。
在C++11标准中引入的支持多线程。想要使用C++11中的多线程,需要 添加 #include
在C++11标准库中对多线程支持以及用于管理线程的函数和类在 中声明,而那些用于保护共享数据的函数和类在其他头文件中声明。
每个线程都必须具有一个初始函数(initial function),新线程的执行在这里开始。
初始线程始于 main(),而新线程始于与创建的thread对象拥有的初始函数。

启动线程

线程是通过构造 std::thread对象来开始的,该对象指定了线程上要运行的任务。在最简单的情况下,该任务仅仅是一个普普通通的返回 void 且不接受参数的函数。这个函数在自己的线程上运行,直到返回,然后线程停止。该任务也可以是一个 函数对象lambda表达式成员函数

传递函数对象为参数

#include 
#include 
using namespace std;
class background_task_1
{
public:

	void operator()()const //无参 重载了operator()函数的类的实例称为函数对象
	{
		std::cout << "background_task_1::operator() ...." << endl;
		
	}
	
	void operator()(int a)const//有参 重载了operator()函数的类的实例称为函数对象
	{
		std::cout << "background_task_1::operator() .... a =" << a << endl;
	}
};
//
int main(int argc, char *argv[])
{
	//thread t1(background_task_1());//编译报错
	background_task_1 task1; //创建一个函数对象
	thread t1(task1);//会启动一个新线程执行函数对象task1里无参的operator()函数
	thread t2(task1,2);//会启动一个新线程执行函数对象task1里有参的operator()(int)函数
	t1.join();//等待t1线程(被调用线程)执行结束完,(调用线程)这里是主线程然后才会再结束。
	t2.join();//等待t1线程(被调用线程)执行结束完,(调用线程)这里是主线程然后才会再结束。
	//总之只要每个新启动的线程都调用join()方法,那么调用线程都会等待所有的线程执行完毕后,才会结束主线程。
}

打印结果:
C++11多线程:线程的创建及启动_第1张图片
注意:thread t1(background_task_1()); 如这样传递一个临时的且未命名的变量,编译器会解释为如下:
声明了函数 my_thread,它接受单个参数(参数类型是指向不接受参数同时返回background_task_1对象的函数的指针),
并返回thread 对象。而不是启动一个新线程。
编译器无法分清该行是要调用构造函数还是定义变量,从而导致出现 parentheses were disambiguated as a function declaration 警
告。
在这里插入图片描述
总之 在传递匿名对象做为实参时,需注意"C++最棘手的解析"。
可以使用以下两种方式
1. 使用强制类型转换
thread t1((background_task_1()));
方式1中额外的括号避免其解释为函数声明,从而让 t1被声明为std::thread类型的变量。
2.使用{}调用构造函数 C++1
thread t1{(background_task_1()};
方式2中使用了新的统一初始化语法,用大括号而不是括号,同样也是声明一个变量。

传递成员函数为参数

class background_task_2
{
public:
	void member_func()
	{
		std::cout << "background_task_2::member_func .... " << endl;
		
	}

	void member_func1()
	{
		std::cout << "background_task_2::member_func1 ...."<< endl;
		
	}

	void member_func1(int b)
	{
		std::cout << "background_task_2::member_func1 .... b = " << b << endl;
	}
};

int main(int argc, char *argv[])
{
	background_task_2 task2;
	thread t1(&background_task_2::member_func,&task2);//member_func 为当前类中唯一此名的函数,不存在重载可以这样传递。
	t1.join();
	return 0;
}

int main(int argc, char *argv[])
{
	background_task_2 task2;
	//member_func1函数存在重载的情况下
	void (background_task_2::*fun1)() = &background_task_2::member_func1;
	void (background_task_2::*fun2)(int) = &background_task_2::member_func1;
	
	thread t2(fun1,&task2);//启动一个新线程,执行member_func1无参函数
	thread t3(fun2,&task2,80);//启动一个新线程,执行member_func1有参函数
	
	t2.join();
	t3.join();
	
	return 0;
}

C++11多线程:线程的创建及启动_第2张图片
在这里插入图片描述

传递全局函数为参数

void global_func(int a)
{
	std::cout << "global_func.... a = " << a << endl;
}

int main(int argc, char *argv[])
{
	thread t1(global_func,30);//函数名,是第一个参数,函数中的参数,依次往后写.
	t1.join();
	return 0;
}

C++11多线程:线程的创建及启动_第3张图片

传递lambda函数为参数

int main(int argc, char *argv[])
{
	//thread t1(global_func,30);
	int a = 10;
	int b = 20;
	thread t1([=]{
		cout << "a + b = " << a+b << endl;
	});
	t1.join();
	return 0;
}

C++11多线程:线程的创建及启动_第4张图片

也可调用std::thread的无参构造

int main(int argc, char *argv[])
{
	thread t1;//单独使用它,没有意思,因没有绑定线程函数。一般用于接收其他线程对象。
	t1.join();//当t1是根据无参构造生成的对象,直接调用join会报错,后面讲joinable时用的
}

join()、joinable()、detach() 等函数

因为一旦开始线程,你需要显示地决定是要等待它完成(通过使用join等待它),还是让它自行运行(通过detach分离它)。
如果在std::thread对象销毁前未做决定,那么你的程序会被终止(thread对象的析构函数调用std::terminate())。
C++11的多线程的执行和Java的多线程的执行不太一样。Java中的多线程不用指定结合它 还是 分离它。

Join函数

Join(等待):
当主线程调用了 join 方法后,它会阻塞直到子线程完成并返回控制权。
主线程在执行 join 后,通常会继续执行后续代码,因为它已经知道子线程的状态。
如果子线程抛出异常,那么主线程会在异常被处理之前接管控制权。
使用 join 时,如果子线程没有完成,主线程将一直处于阻塞状态直到子线程结束。

int main(int argc, char *argv[])
{
	//thread t1(global_func,30);
	int a = 10;
	int b = 20;
	thread t1([=]{
		cout << "a + b = " << a+b << endl;
	});
	t1.join();//主线程会阻塞到这里,等待子线程执行完毕后,再执行下面一句
	cout << "main..." << endl;
	return 0;
}

另外一个例子:
假设有两个线程,线程A和线程B。线程A被托管在thread对象A中。在线程B中执行对象A的join()函数,那么线程B就会被阻塞住,直到线程A执行完成后,线程B才会执行A.join()后面的代码。

//使当前线程睡眠的封装函数
void Sleep(long ms){
	std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

void f(){
    Sleep(1000);//会休眠1秒
    cout<<"I am f thread"<<endl;
}

int main()
{
    thread t1(f);//线程A被托管在thread对象t1中
    t1.join();//线程B 指的就是 mian函数主线程
    cout<<"run main thread"<<endl;
}

C++11多线程:线程的创建及启动_第5张图片
但是如果创建一个空的thread对象

int main()
{
    thread t1;
    t1.join();
    cout<<"run main thread"<<endl;
}

C++11多线程:线程的创建及启动_第6张图片
空的thread对象,其实是没有持有线程资源,这种情况是不能join的。

detach 函数

Detach(分离):
detach 操作使得子线程不再需要与主线程同步,它可以独立地执行自己的任务。
子线程一旦进入终止状态,无论是否使用了 detach,都会自动从系统中移除。
使用 detach 的线程通常不会被主线程回收资源或杀死,除非有其他线程尝试进行这些操作。
detach 通常用于多线程编程中,减少主线程因为等待子线程而停滞的时间。
总结来说,join 是用来等待并接收子线程的控制权的,而 detach 是用来使子线程成为独立的进程,不再需要主线程的管理。这两种方法适用于不同的情况,选择哪一种取决于你的需求。

int main(int argc, char *argv[])
{
	//thread t1(global_func,30);
	int a = 10;
	int b = 20;
	thread t1([=]{
		cout << "a + b = " << a+b << endl;
	});
	t1.detach();//无须等待子线程的执行完毕,主线程会立即执行下一句
	cout << "main..." << endl;
	return 0;
}

C++11多线程:线程的创建及启动_第7张图片

joinable函数

Joinable(可结合)
joinable()函数的功能,官方文档说的非常晦涩难懂。我个人比较倾向于这种解释:" 判断当前的线程对象是否是可执行线程对象。"
一般有三种情况,joinable()返回false:
(1)该thread对象通过无参构造函数构造;
(2)该thread对象执行过join()或者detach();
(3)该thread对象被move过,但是move的接受方的joinable()结果为true。

void f5(){

};

void f5(){

};

int main()
{
    thread t1;
    thread t2(f5);
	cout<<"t2 : "<<t2.joinable()<<endl;
    t1=move(t2);//调移动构造函数
    cout<<"t1 : "<<t1.joinable()<<endl;
    cout<<"t2 : "<<t2.joinable()<<endl;
    t1.join();
};

C++11多线程:线程的创建及启动_第8张图片
注意:t2在这里把线程资源权移交给了t1,所以t2在被析构时,其joinabe() 返回false。所以不用使用t2.join() 或者 t2.detach();时也可以正常被析构。不会报 terminate called without an active exception。

Join函数到底干了什么?

一个非常容易忽略的点:join函数实际上是放弃了持有的线程资源。对于没有持有线程资源的thread对象不能join,这是为了保证thread对象只能join一次。joinable函数实际上是检测thread对象是否持有线程资源。
假如说我们创建了一个空的thread对象,没有传入函数,自然不会持有操作系统层面的线程资源。强行join就会报错。
假如我们给它move进来一个资源以后,那么它就可以join了。
这个对象在执行了join函数以后,彻底放弃了对于线程资源的管理权,joinable状态变成了false。
同样的道理,thread被move以后,放弃了线程资源,joinable状态变成了false。
一句话:joinable()返回值为true的对象,也就是持有线程资源的对象,才能join。

必须join或者detach吗?

从宏观上看,是的。
一句话描述:C++程序中,在thread对象执行析构函数的时候,不允许持有线程资源。执行析构之前,要么通过join放弃资源的控制权,要么将线程资源move出去。

std::thread类的析构源码

~thread() _NOEXCEPT
{	// clean up
	if (joinable())
		_XSTD terminate();
}

即 当系统在调当前线程对象的析构函数时,若joinable()返回true(表明当前线程即没有join或没有detach 或没有移交线程资源),则会调terminate() 终止程序。

线程资源不能被覆盖

void f6(){
}
int main()
{
    thread t1(f6);
    thread t2(f6);
    t1=move(t2);   //覆盖t1持有的线程资源
    cout<<"t1 : "<<t1.joinable()<<endl;
    cout<<"t2 : "<<t2.joinable()<<endl;
    t1.join();
}

C++11多线程:线程的创建及启动_第9张图片
可以看到当t1持有线程资源时,是不能被t2的线程资源覆盖的。

你可能感兴趣的:(C,and,C++的笔记,c++,算法,开发语言)