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

相关推荐
码匠许师傅7 小时前
【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?
c++·面试
BUG研究员_8 小时前
Runnable与LCEL
开发语言·人工智能·python
果果燕10 小时前
实习笔记(一):NFS、Samba、VNC、Git、CMake、systemd、内核、交叉编译
linux·arm开发·c++·笔记·git
牛艺翔10 小时前
C++基础
开发语言·c++
键盘会跳舞10 小时前
C++ :容器适配器stack源码级拆解
开发语言·c++··stack·先进后出
美味蛋炒饭.10 小时前
Git 版本控制(下)
开发语言·git·学习·总结·后端开发
我星期八休息11 小时前
网络编程—网络层
开发语言·前端·网络·人工智能·智能路由器
问商十三载11 小时前
RAG 检索效果差怎么排查?2026 五层诊断法完整指南
开发语言·人工智能·windows·python·算法
不负岁月无痕11 小时前
简单理解操作系统结构
java·linux·c语言·开发语言·c++·面试
余额瞒着我当琳11 小时前
C++模板初阶与STL初探
android·java·c++