C++11线程库的使用(上)

1.线程库的基本使用

在C++11中引入了原生的线程支持,包含在<thread>头文件中,我们下面来看看该线程库的基本使用方法,先看下面这段程序。

cpp 复制代码
#include <iostream>
#include <thread>

void show(void)
{
	std::cout << "hello thread" << std::endl;
}

int main()
{
	std::thread thread(show);
	std::cout << "main" << std::endl;
	return 0;
}

我们创建了一个线程对象,将show函数作为入口传入。当我们运行时有以下运行结果:

虽然我们的hello thread成功输出出来了,但是却出现了错误,这是因为在我们子线程还没有结束时,我们的main线程已经return结束啦,就导致出现了错误。那么怎么办呢?我们来看下面这段程序:

cpp 复制代码
#include <iostream>
#include <thread>

void show(void)
{
	std::cout << "hello thread" << std::endl;
}

int main()
{
	std::thread thread(show);
	std::cout << "main" << std::endl;
	thread.join();
	return 0;
}

我们再来看运行结果:

可见我们在main结束前加入了一句thread.join()就解决了这个问题,这是因为当这个调用时一个阻塞调用,主线程运行到这里后就会被阻塞,只有当子线程结束后主线程才能进行往下执行。所以就解决了上面子线程还没有结束主线程就结束了导致的报错,我们将上面的程序修改一下会有更明显得体现,如下:

cpp 复制代码
#include <iostream>
#include <thread>

void show(void)
{
	for (int i = 0; i < 100000; i++)
	{
		std::cout << "hello thread" << std::endl;
	}
}

int main()
{
	std::thread thread(show);
	thread.join();
	std::cout << "main" << std::endl;
	return 0;
}

运行结果为:

可见mian的输出是在子线程输出完后再输出的。当然我们还可以是用另一个方法来解决上面报错问题。

cpp 复制代码
#include <iostream>
#include <thread>

void show(void)
{
	std::cout << "hello thread" << std::endl;
}

int main()
{
	std::thread thread(show);
	std::cout << "main" << std::endl;
	thread.detach();
	return 0;
}

运行结果为:

我们可以看到确实没有报错了,但是我们的hellothread确没有被输出出来。这是因为当主函数运行到thread.detach时子线程和主线程分离了,子线程进入后台执行,而主线程后面就立即结束了,所以就没有输出,但是主线程结束后子线程还可以自动在后台运行。接着我们来看看当要传入参数怎么书写程序:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
using namespace std;

void show(string s)
{
	cout << s << endl;
}

int main()
{
	thread thread(show,"hello thread");
	cout << "main" << endl;
	thread.join();
	return 0;
}

我们只需要在创建thread对象,传入一个所需的参数即可。看运行结果:

2.线程使用过程中的易错问题

(1)线程间传递临时变量

我们来看下面的程序:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
using namespace std;

void process(int& n)
{
	n += 1;
}

int main()
{
	int data = 1;
	thread thread(process,data);
	thread.join();
	cout << "data=" << data << endl;
	return 0;
}

我们想启用一个线程process对我们创建的data进行加1操作时用上面的程序能不能做到呢,看运行结果:

可见直接出现了错误,这是因为我们在线程间传递临时变量出现的错误,我们通过std::ref来解决该问题程序如下所示:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
using namespace std;

void process(int& n)
{
	n += 1;
}

int main()
{
	int data = 1;
	thread thread(process,ref(data));
	thread.join();
	cout << "data=" << data << endl;
	return 0;
}

(2)传递指针和引用时指向局部变量

我们看下面这段程序:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
using namespace std;

thread t;
void process(int& n)
{
	n += 1;
}

void func(void)
{
	int data = 1;
	t =  thread(process, ref(data));
}

int main()
{
	func();
	t.join();
	return 0;
}

我们可以知道data局部变量在func函数结束后会被释放,所以等到引用n在使用data那块空间时已经被释放了所以就会出现报错,指针也是一样的,那么怎么解决呢?我们可以将data定义为全局变量即可。

(3)类成员函数作为入口参数,类对象被提前释放

我们看下面这段程序:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
using namespace std;


class A {
public:
	void show()
	{
		cout << "A" << endl;
	}
};


int main()
{
	A a;
	thread t(&A::show,&a);
	return 0;
}

当线程的入口参数时类成员函数时,我们还要将类的对象的地址也作为参数传入。这里因为我们创建一个对象所以我们要进行销毁,但是销毁的时机我们却不好把握。这里如果当对象a被提前销毁了,而线程想要使用类的成员函数就会出现问题。对于这个问题我们可以使用智能指针的方法来解决。看下面的程序:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
#include <memory>
using namespace std;

thread t;
class A {
public:
	void show()
	{
		cout << "A" << endl;
	}
};



int main()
{
	shared_ptr<A> a = make_shared<A>();
	thread t(&A::show, a);
	t.join();
	return 0;
}

对于智能指针来说,当其在没有地方使用时就会自动释放掉,既不会造成内存的泄露,也能更好的让线程执行类的成员函数。

(4)使用类的私有成员函数作为线程的入口参数

对于这个问题,我们可以使用友元函数进行解决,如下:

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
#include <memory>
using namespace std;

thread t;
class A {
private:
	friend void func();
	void show()
	{
		cout << "A" << endl;
	}
};


void func()
{
	shared_ptr<A> a = make_shared<A>();
	thread t(&A::show, a);
	t.join();
}


int main()
{
	func();
	return 0;
}
相关推荐
一条咸鱼_SaltyFish几秒前
[Day12] 合同审查引擎开发中的技术挑战与解决之道 contract-review-engine
开发语言·人工智能·程序人生·开源软件·ddd·个人开发·ai编程
zho_uzhou1 分钟前
倍福指针使用——始终为字节形式
开发语言
郑州光合科技余经理1 分钟前
开发实战:海外版同城o2o生活服务平台核心模块设计
开发语言·git·python·架构·uni-app·生活·智慧城市
Kratzdisteln2 分钟前
【Python】Flask 2
开发语言·python·flask
过河卒_zh15667663 分钟前
喜讯:第十五批生成合成类算法备案备案号公布
人工智能·算法·aigc·生成式人工智能·算法备案
cpp_25017 分钟前
B3927 [GESP202312 四级] 小杨的字典
数据结构·c++·算法·题解·洛谷
abcd_zjq7 分钟前
VS2022+QT6.9配置ONNXruntime GPU、CUDA、cuDNN(附官网下载链接)(GPU开启代码示例)
qt·visual studio·cuda·onnx
Cx330❀9 分钟前
《C++ 递归、搜索与回溯》第2-3题:合并两个有序链表,反转链表
开发语言·数据结构·c++·算法·链表·面试
AI科技星10 分钟前
电磁耦合常数Z‘的第一性原理推导与严格验证:张祥前统一场论的几何基石
服务器·人工智能·线性代数·算法·矩阵
AI科技星14 分钟前
电场起源的几何革命:变化的引力场产生电场方程的第一性原理推导、验证与统一性意义
开发语言·人工智能·线性代数·算法·机器学习·数学建模