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

发布于:2024-04-28 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

等待线程的完成

传递参数

属性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


网站公告

今日签到

点亮在社区的每一天
去签到