C++并发编程 - 线程管理

目录

等待线程的完成

传递参数

属性API

Reference


我们的std::thread对象就是在创建的时候方才执行函数,而不是使用某一个类似于run的API执行。这使得我们启动一个线程的方式变得很简单:

复制代码
void task(void); // A demo
std::thread(task);

我们这样就派生出了一个执行流task。

出于C++语法解析的原因,有的时候我们会遇上定义-声明混淆的问题。比如说假使我们传递了一个自己定义的仿函数:

复制代码
struct MyTask()
{
    void operator()(){
        do_one_thing(); // Decleared anyhow!
    }
    
    void do_one_thing(){/* ... */}
}
​
int main()
{
    MyTask t;
    std::thread task(t());
}

这里会被认为是声明了一个带有一个参数,返回值是std::thread的名称是task的函数。所以一个比较号的回避办法是:

显式告知我们调用的是std::thread的构造函数:

复制代码
std::thread task( (t()) );

或者是使用统一大括号法:

复制代码
std::thread task{ t() };

原书(《C++并发编程》)还提到了使用lambda,不失为一种方法,但是总有种脱裤子放屁的美感:

复制代码
std::thread task([](){
    t(); // 或者把里头的东西展开写了    
})

必须值得注意的是,我们更多时候可能使用的是带有参数的函数,这也就意味着,如果这些参数是引用形式进行传递的,势必要保证这些参数是有效的!

复制代码
struct MyFunc
{
    int& f;
    func(int& _i):f(_i){}
    void operator()(){
        for(int i = 0; i < 100000000; i++)
            do_someThing(f); // 极有可能会崩溃
    }   
}
​
void shitBro(){
    int some_local_st = 0;
    MyFunc f(some_local_st);
    std::thread t(MyFunc);
    t.detach(); // 这里表示的是脱离主线程称为一个后台线程
                // 结果可想而知,在这里函数出去,我们的对象引用这里就会失效!
}

等待线程的完成

一个线程结束他的作用域的时候,如果我们不指定线程的处理后事,程序可能会抛出一个异常来!所以,想要让一个线程结束作用域时仍然可以存在,只能两种选择

等待线程执行完毕(也就是std::thread的join方法)

脱离主线程成为后台进程(也就是std::thread的detach方法)

当然,还有一种办法就是将std::thread封装,我们实现自己默认行为的std::thread

复制代码
#include <iostream>
#include <type_traits>
#include <thread>
​
class MyThread
{
    std::thread _thread;
public:
    MyThread() = delete;
    template<typename Callable, typename... Args>
    MyThread(Callable&& c, Args... a)
    {
        static_assert(std::is_invocable_v<Callable, Args...>, "Is not Invookable! Check Params");
        _thread = std::move(std::thread(std::forward<Callable>(c), std::forward<Args>(a)...));
    }
​
    ~MyThread()
    {
        if(_thread.joinable()){
            _thread.join();
        }
    }
};
​
void printHello(){
    std::cout << "Hello";
}
​
​
int main()
{
    MyThread m(printHello);
}

这就允许我们可以执行默认的行为,当然这不过是一个小demo,还有很多方法并没有实现,读者可以自行扩展之!

传递参数

我们注意到std::thread的参数无非就两处!第一个参数:可以被invoke(调用的),第二组是一个参数包,它表明了被调用的对象所需要的参数。

复制代码
void task(int param, double t);
int d = 20;
double f = 30;
std::thread t(task, d, t);

如你所见,如果你查看std::thread的参数包中的声明类型是:Args...,这证明了我们传递的参数都是以拷贝的方式进行传递,这正是为了防止潜在的引用失效。

如果我们确实在移动一个巨大的对象,则可以手动触发std::move的方式提醒使用重载的参数移动构造的构造函数!

属性API

  • get_id():获取线程的ID,它将返回一个类型为std::thread::id的对象。

  • joinable():检查线程是否可被join。(我们没有办法对一个detach的线程做join!所以一个泛化的Join方法是)

    复制代码
    if(t.joinable()) // 是不是已经detach了?
        t.join()    // 不是detach的,选择join汇入主线程
  • std::thread::hardware_concurrency:我们知道并发度过高不会增加系统的效率(好奇为什么看我操作系统的关于进程调度的博客)所以我们需要确定当前系统的一个合适线程数目上限

Reference

std::thread - cppreference.com

相关推荐
死磕音视频4 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
WL学习笔记6 小时前
C & C++ 内存管理
c++·c++内存管理
顽疲7 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog7 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友
宛 禾8 小时前
Qt 控件入门:QLabel 从拖拽到对象树,一篇讲透
c++·qt·学习
hetao17338379 小时前
联盟场-提高组-R7 & ZYZSC-S-Round 11
c++·算法
程序喵大人9 小时前
【C++入门】编译链接模型 - 07 链接器怎样把符号对上号
开发语言·c++·编译器·编译链接·链接器
微三云 - 廖会灵 (私域系统开发)9 小时前
供应链系统实战:多渠道订单聚合与分布式库存一致性方案总结
开发语言·分布式·php
局部有解9 小时前
Hot100 - 三数之和
数据结构·c++·算法
学逆向的9 小时前
C 数据溢出与精度丢失
c语言·开发语言·网络安全