线程管理

线程管理#

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做了什么:

  1. 拷贝字符串指针到内存空间。
  2. 通过字符串指针构造一个临时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(),可以获得当前线程的标识。

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718225

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示