为什么要使用多线程?
- 为了提高程序执行的速度,可以将程序划分成多个相对独立的任务并发执行,实现并发可以采用多进程和多线程。多线程是指在一个进程中同时运行多个线程,线程共享进程的地址空间和系统资源。多线程方法中线程之间的切换开销较小,执行效率较高。
注意:C++11才引入多线程支持。
进程与线程
- 一个进程中的线程分为主线程和子线程。
- 主线程是程序的起点,负责创建和管理其他线程。它通常执行程序的初始化操作,包括分配资源和设置环境,以及负责执行最后的关闭动作。
- 子线程是由主线程创建的额外线程,它们可以并行执行,独立于主线程,通常用于执行耗时的任务,以避免阻塞主线程。
- 一个进程只有一个主线程,要实现多线程,需要在主线程中额外创建子线程。
如何创建和管理子线程?
子线程执行普通函数
#include <iostream>
#include <thread>
int main()
{
std::thread t([](){
std::cout << "Hello World!" << std::endl;
});
t.join();
return 0;
}
子线程执行带有非常量左值引用参数的函数
#include <iostream>
#include <thread>
using namespace std;
static void ChangeValue(int& x, int val) {
x = val;
}
int main()
{
thread th[100];
int nums[100];
for (int i = 0; i < 100; i++)
th[i] = thread(ChangeValue, nums[i], i + 1);
for (int i = 0; i < 100; i++) {
th[i].join();
cout << nums[i] << endl;
}
return 0;
}
- 编译上述代码会报错,可以用
std::ref
对引用参数进行包装解决。主要原因是std::thread
的构造函数会对参数进行移动操作,而可调用对象左值引用参数不接受右值引用,因此会报错。使用std::ref
可以解决问题的原因是std::ref
会生成一个reference_wrapper
类型的临时对象,该对象重载了左值引用类型转换操作符,可以隐式转换为左值引用,而且由于是临时对象,移动操作相当于没有操作。
使用detach分离主线程和子线程
- 使用
detach
方法后主线程不再等待子线程执行完,而是立即返回到主线程继续执行后续的代码,子线程结束时自行负责回收资源。
注意:使用detach时,应确保子线程中的操作能够在主线程退出前完成,或者子线程中申请的资源能够正确释放。#include <iostream>
#include <thread>
#include <chrono>
void threadFunction() {
std::cout << "开始执行子线程" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "子线程结束" << std::endl;
}
int main()
{
std::cout << "开始执行主线程" << std::endl;
std::thread t(threadFunction);
t.detach();
std::cout << "继续执行主线程" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "主线程执行完毕" << std::endl;
return 0;
}
- 执行上述代码,得到以下结果。没有子线程结束的提示。
开始执行主线程
继续执行主线程
开始执行子线程
主线程执行完毕