线程管理
线程管理#
std::thread
可以通过有函数操作符类型的实例进行构造。
#include <thread>
class Task {
public:
void operator()() const {
dosomething();
}
};
Task task;
std::thread mythread(task);
使用一个有括号操作符函数的自定义类作为线程对象的构造参数,构造出来的线程对象会调用这个类的括号操作符函数,也就是说,task
对象会被复制到mythread
的线程空间中,函数对象的执行和调用都是在t
的线程空间中完成。
当把函数对象作为构造参数传入线程对象时,存在一个语法解析上的问题。如果你传递了一个临时变量,而不是一个命名变量,C++解析器会将其解析成函数声明,而不是类型对象的定义。
std::thread mythread(Task());
这样相当于声明了一个函数名为mythread
,有一个函数指针参数(指向一个没有入参,返回值是Task对象), 返回值是std::thread对象。
解决办法是再加一个括号,或者使用初始化语法。使用lambda表达式也可。
std::thread mythread((Task()));
std::thread mythread({Task()});
使用join()
等待线程执行完成。调用join()
后,会清理线程相关的内存,所以每个线程对象只能调用一次,之后就不可以再调用。调用joinable()
可以查看当前线程对象是否还可以join。
使用detach()
分离线程,分离线程后当前线程将不再等待分离出的线程是否执行完成(例如在main
函数中分离线程,并直接退出main
函数,在分离的线程中的cout
就无法输出了)
如果在
join()
被调用前有异常被抛出,如果没有捕获处理的话,会导致执行join()
的部分被跳过,线程在程序完全结束前永远无法被释放,从而导致线程对象的生命周期出现问题。
参数传递#
向线程对象传递参数只需要在函数对象后面继续追加参数就好了,但是需要注意的是,后面附加的参数都将会是以拷贝的形式(左值或右值)传递到线程的内存空间里,无论线程对象绑定的函数入参是否是引用。
void fun(std::string& s);
std::thread my_thread(fun, "hello, world");
线程对象的构造参数追加的是一个字面量字符串,通过这个字符串构造了一个string
的临时变量传入到了my_thread
线程的内部。
这样会存在一个问题。
void fun(std::string& s) {
do_something();
}
void make_thread(int num) {
char buffer[1024]{"maybe undefined"};
std::thread problematic_thread(fun, buffer);
problematic_thread.detach();
}
在构造线程的函数中构造了一个线程对象,绑定的执行函数入参是一个string类型,实际传入的是一个字符串,会调用string类的构造,生成一个临时的string对象传给要执行的函数中,逻辑上没有问题的。
但是,再仔细考虑一下problematic_thread
做了什么:
- 拷贝字符串指针到内存空间。
- 通过字符串指针构造一个临时string对象,再传递给绑定的函数。
由于传入的指针指向的是函数内的局部变量,在这两步之间就可能出现问题了,在将指针拷贝到线程空间后,原有的局部变量可能被释放了,然后线程对象又执行了string的构造,这就造成了未定义的行为。
有效的解决方法是明确传入的类型。
void make_thread(int num) {
char buffer[1024]{"maybe undefined"};
std::thread safe_thread(fun, std::string(buffer));
problematic_thread.detach();
}
传递引用
如果要向线程中传递引用,需要使用std::ref()
显式地将参数指定为引用形式。这和标准库中的std::bind()
使用类似。
转移线程所有权#
类似于std::unique_ptr
,线程对象也应该是独有的,仅支持通过std::move()
转移所有权。
机器的线程数量#
标准库中提供了std::thread::hardware_concurrency()
接口,用于获取当前机器的可并发的线程数量,一般等于cpu的核心数量。返回值只是一个数值,当无法获取时,会返回0。
线程标识#
线程的标识是一个std::thread::id
类型,可以通过get_id()
来获取。
- 如果是线程对象调用,
onethread.get_id()
,会返回该线程对象的标识,对象如果没关联任何执行线程,会返回std::thread::type
的默认构造值,标识“没有线程”。 - 还可以直接在当前线程使用
std::this_thread::get_id()
,可以获得当前线程的标识。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话