c++多线程之std::thread类的使用

c++多线程之std::thread类的使用


c++中封装了用于创建线程的类thread,它需要包含头文件 i n c l u d e < t h r e a d > include <thread> include<thread>,我们来看一下thread中的几个重要接口。

构造函数

1.默认构造函数thread t()构造空的线程对象,基本不使用
2.初始化构造函数template <class Fn, class… Args>explicit thread (Fn&& fn, Args&&… args)构造执行函数fn的线程对象,args代表函数f的参数列表
3.拷贝构造函数thread (const thread&) = delete被禁用
4.移动构造函数thread (thread&& x)一般不使用

下面我们具体介绍一下其各种构造函数的使用:

1.默认构造函数,这个没有什么意义,我们基本不使用。
2. 初始化构造函数,这是我们使用最频繁的构造函数了,我们基本在定义时便决定了这个线程的任务,它的使用也非常简单,第一个参数fn代表这个线程的入口函数,若这个入口函数有参数,将其按序写到fn后面。
3. 移动构造函数,这个除非必须不然也不推荐使用,它是指我们构造一个获取另一个线程x的线程对象,此时x不代表任何线程。
看下面代码:

#include<iostream>
#include<thread>
#include<cstdlib>
using namespace std;

void fn1()
{
	cout << "----this is a thread for fn1 with no arguments----\n";
}

void fn2(int n)
{
	cout << "----this is a thread for fn2 with arguments----\n";
	for (int i = 0; i < n; i++)
	{
		cout << i << " ";
	}
	cout << endl;
}

class fn
{
public:
	fn() {};
	~fn() {};

	void fn1()
	{
		cout << "----this is a fn1 within class of fn with no arguments----\n";
	}

	void fn2(int n)
	{
		cout << "----this is a fn2 within class of fn with arguments----\n";
		for (int i = 0; i < n; i++)
		{
			cout << i << " ";
		}
		cout << endl;
	}
private:

};



int main()
{
	fn f;
	thread t1(fn1);
	thread t2(fn2,5);
	thread t3(&fn::fn1,&f);
	thread t4(&fn::fn2, &f, 5);

	getchar();
}

上述是几种最常用的创建线程的方式,我们可以创建无参数的线程,也可以创建有参数的线程,还可以传入类对象的公有函数作为入口函数,除此之外我们还可以在类的内部创建线程,大家可以自己尝试创建一下。上面的代码大家可以编译一下是成功的,但运行发现出现问题。这是为什么呢?因为线程在创建后我们必须决定这个线程与主线程的关系,这时便需要用到thread中的两个接口,join()和detach()。

连接线程

join()表示连接线程,线程执行完成后,该函数返回,然后才能执行join()语句的后续代码。需要注意的是一个线程只能被连接一次,被连接后便不可再被连接。看下面代码:

int main()
{
	thread t2(fn2,100);
	t2.join();
	thread t1(fn1);
	t1.join();
	getchar();
}

这段代码的输出为:

----this is a thread for fn2 with arguments----
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
----this is a thread for fn1 with no arguments----

我们发现t1线程一定会等待t2线程结束后才会执行。这是因为t2.join()将主线程的后续程序阻塞,直到线程t2返回。我们将t2.join()换一下位置。

int main()
{
	thread t2(fn2,100);
	thread t1(fn1);
	t1.join();
	t2.join();
	getchar();
}
----this is a thread for fn2 with arguments----
0 1 2 3 ----this is a thread for fn1 with no arguments----
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

我们发现t1在t2输出的时候输出了,这是因为此时这两个程序都被创建,并且这两个线程之间没有被相互阻塞,它们各自独立运行,但主程序仍然必须等待两个线程返回才能返回。join()函数主要用来实现子线程与父线程之间的操作同步。

分离线程

detach()表示分离,从调用线程中分离出对象所代表的线程,此时它们彼此独立执行,不会阻塞任何线程也不可以任何方式进行同步,也就意味着这个线程不再可控,所以要谨慎使用,当执行结束时,其资源将被释放。调用此函数后,线对象变为不可连接,可以安全销毁。看下面代码:

int main()
{
	thread t2(fn2,10000);
	t2.detach();
	thread t1(fn1);
	t1.join();
	getchar();
}

我们可以尝试运行一下,在t2执行完毕前按回车,发现主线程返回了,不会被阻塞,这时因为t2已经和主线程分离,主线程只需执行后续程序正常返回即可。这样是有风险的,因为我们失去了t2的控制并且不知道它运行到什么位置,有可能导致t2线程在运行完毕之前其资源被释放,这种情况并不会报警。

移动线程

移动线程其实就是指移动构造函数,有必要单独讲一下,因为它和拷贝构造函数容易混淆。线程在创建时是可以被移动的,也就是把一个线程中的程序移动到另一个线程中。看下面代码:

int main()
{
	thread t1=thread(fn2, 10);
	t1.join();

	getchar();
}

输出:

----this is a thread for fn2 with arguments----
0 1 2 3 4 5 6 7 8 9

这里主程序其实构造了两个线程,"="号右边构造一个线程然后将其移动到t1中,注意此时并不是拷贝,因为拷贝需要右值是一个已经被初始化的实例对象,如下面代码。

int main()
{
	thread t1=thread(fn2, 10);
	thread t2(t1);
	thread t3=t1;
	getchar();
}

此时t2和t3通过t1拷贝,我们可以编译一下,这两种初始化方式都不能通过。线程为什么不能被拷贝的原因在这里不细讲,大家记住就行。普通的线程创建会这些就足够了,但线程不只是简单创建出来就行,它更多的是需要进行线程间的同步或互斥,共享数据空间实现某些任务,这就需要互斥锁和条件变量的加入,我们将在后续讲解。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值