C++20中的jthread

一、多线程开发

c++11以前,是不包含线程库的,开发多线程,必须使用OS系统自带的线程API,这就导致了很多问题,最主要的是,跨平台的开发,一般要保持几个主流应用的库的代码支持,特别是对Win平台和Linux平台的两个线程库的支持,否则,就无法实现简单的跨平台的功能。而两个平台的多线程的使用差异和一些细节不同的,比如事件Event在Linux上没有。而条件变量在Win平台上没有,就更容易使一些应用场景的开发的复杂性大幅提高。
而到了c++11以后,提供了std::thread这个多线程的支持,可以说大幅的降低了跨平台开发的复杂性。
但是在实际的应用过程中,又遇到了一些新的需求变化,比如应用的方便性和多线程运行过程中的可操作性及可见性上,都需要提供新的接口。按照c++设计者们的一贯作风,他们不会在基础的std::thread本身进行修改,那么,他们就对这个类进行了再封装,增加了上述的功能,然后提出了std::jthread这个类。

二、jthread的功能和实现

一般来说,接口越丰富,那么这个类功能越强大,但是反过来,缺点就是不容易掌握并且容易忘记调用,在使用std::thread时,有没有忘记调用join()或者detach()的时候?自动调用好不好?线程函数运行过程中,可不可以可以动态请求交互一下,而不是靠各种变量来预先进行控制。对,在std::jthread上都实现了。
看一看标准文档是如何定义的:

std::jthread::jthread
jthread() noexcept;                              (1)	(C++20)
jthread( jthread&& other ) noexcept;             (2)	(C++20)
template< class Function, class... Args >
explicit jthread( Function&& f, Args&&... args );(3)	(C++20)
jthread( const jthread& ) = delete;              (4)	(C++20)

第一个表示创建新的jthread对象;第二个表示移动语义,既然移动了,那么原来线程停止执行线程,由新线程执行;第三个表示线程函数f接受 std::stop_token 作为其首参数,则新线程开始执行:

std::invoke(decay_copy(std::forward<Function>(f)),
            get_stop_token(),
            decay_copy(std::forward<Args>(args))...);

;否则它开始执行

std::invoke(decay_copy(std::forward<Function>(f)),
            decay_copy(std::forward<Args>(args))...);

任一情况下, decay_copy 定义为

template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }

除了在调用方的语境中执行 decay_copy ,故而在求值和移动/构造参数期间抛出的任何异常都在当前线程抛出,而不会开始新线程。
构造函数的完成同步于(按 std::memory_order 中定义) f 的副本在新线程上调用的开始。
若 std::remove_cvref_t 与 std::jthread 为相同类型则此构造函数不参与重载决议。
最后一个表示不可复制,也就是说只能它自己执行线程,没有任何其它一个std::jthread对象可以表示同一线程。

std::jthread的成员函数里,可以看支持查看硬件并发数的函数,交换线程对象的函数,协作请示的函数等,更多的详情可以参看一下:
https://zh.cppreference.com/w/cpp/thread/jthread/jthread
https://zh.cppreference.com/w/cpp/header/stop_token

三、应用

线程的中断和取消是需要高度引起注意的,一不小心,强行KILL的线程中的各种资源或者句柄就没有回收,导致各种泄露。这也是在多线程编程中,普遍有一个要求,就是让线程自己执行完毕退出,而为了这个要求,就需要牺牲一下控制的效率。
当然,先从std::thread的多线程编程的例子看:

#include <Windows.h>
#include <iostream>
#include <thread>
#include <chrono>

bool quit = false;

void SleepTime(int sec)
{
    std::this_thread::sleep_for(std::chrono::seconds(sec));
}
void ThreadWorker()
{
    //work work work
    std::cout << "thread start,working! " << std::endl;
}

void MainWorker()
{
    std::cout << "main thread start,working! " << std::endl;
}
int main()
{
    std::thread t = std::thread([&]() {
        while (!quit)
        {
            ThreadWorker();
            SleepTime(2);
        }
    });

    //Sleep(3);
    MainWorker();
    t.join();//t.detach();
}

这个DEMO非常简单,在主线程中启动了一个子线程,需要注意一下注释的延时的情况,主要是起一个先后交替的条件。主线程在执行完成自己的工作后,就会join等待子线程的完成,像上面这种情况,就死循环了,不会停止。如果需要停止只能使用设置quit为True或者强制的命令等不友好的手段了。首先看一下,换成jthread会是啥样?其它代码都没有改变,只是修改了下面的代码:

std::jthread t = std::jthread([&]() {
    while (!quit)
    {
        ThreadWorker();
        SleepTime(2);
    }
});

需要包含的jthread等的头文件可以去github或者相关的网站上下载,这里只提供一处,并对此表示感谢:
https://github.com/josuttis/jthread/tree/master/source
上面的两处代码的运行结果是完全一样的,没有什么不同。那用这个类的优势呢?别急,把main函数改一下:

int main()
{
    std::jthread t = std::jthread([&]() {
        while (!quit)
        {
            ThreadWorker();
            SleepTime(2);
        }
    });

    Sleep(3);
    MainWorker();
    //t.join();//t.detach();
}

把最后一行注释,再编译运行,仍然没有啥问题,有一点不同了吧。这可以避免粗心大意的同学出现意外错误吧。再看一下,如果设置quit为true,那么线程会怎么办?没啥大问题,到了判断就退出了,可是如果有多个线程在执行,是不是要设置多个类似的变量呢?答案是肯定的。这时候儿看看jthread的协作中断:

int main()
{
    std::jthread t = std::jthread([&]() {
        while (!quit)
        {
            ThreadWorker();
            SleepTime(2);
        }
    });

    Sleep(3);
    MainWorker();
    t.request_stop();//简单增加一行代码
}

编译运行,发现没啥反应,好,再修改一下线程函数:

int main()
{
    std::jthread t = std::jthread([&](std::stop_token st) {
        while (/*!quit*/!st.stop_requested())
        {
            ThreadWorker();
            SleepTime(2);
        }
    });

    Sleep(3);
    MainWorker();
    //getchar();
    t.request_stop();
    //t.join();//t.detach();
    getchar();
}

这个时候儿发现没啥问题了,注意上面getchar()函数的位置,如果在后面,则执行一次就退出了。如果在前面,就继续不断执行。把t.request_stop();这一行也注释,发现程序仍然和不注释一样,看来这个jthread里做了什么运作,看一下它的析构函数:

inline jthread::~jthread() {
  if (joinable()) {   // if not joined/detached, signal stop and wait for end:
    request_stop();
    join();
  }
}

明白了吧。至于为什么不用detach(),可能是大牛们觉得join()会更安全一些吧。毕竟等待完成后,所有的对象都被回收了。下面再看官网提供的一个例子:

class foo
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 3 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
int main()
{
    foo f;
    std::jthread t3(&foo::bar, &f);
    std::jthread t = std::jthread([&](std::stop_token st) {
        while (/*!quit*/!st.stop_requested())
        {
            ThreadWorker();
            SleepTime(2);
        }
    });
    std::cout << t.hardware_concurrency() << std::endl;

    Sleep(3);
    MainWorker();
    getchar();
    t.request_stop();
    //t.join();//t.detach();
}

执行一下,就可以看出上面提到的decay_copy这个模板函数的使用,没啥不容易理解的。

4、总结

其实还有几个特点值得说说,但不准备再讲了,虽然已经投票完成确定了c++20的标准,但毕竟还没有正式增加进去,大家有兴趣可以看看源码。整体来看,c++应用还是越来越方便,这对c++来说,肯定是个好事情。现在就希望>VS2019或者>GCC11中,尽快完全支持。

相关推荐

  1. C++20jthread

    2024-04-01 22:06:04       12 阅读
  2. c++20jthread再谈

    2024-04-01 22:06:04       14 阅读
  3. 19.C++20std::latch和std::barrier

    2024-04-01 22:06:04       21 阅读
  4. C++23种设计模式

    2024-04-01 22:06:04       8 阅读
  5. 跟我学c++中级篇——再谈C++20协程

    2024-04-01 22:06:04       26 阅读
  6. 探究C++20协程(4)——协程调度器

    2024-04-01 22:06:04       9 阅读
  7. <span style='color:red;'>c</span>++<span style='color:red;'>20</span>

    c++20

    2024-04-01 22:06:04      28 阅读
  8. Ubuntu20.04Pyqt4

    2024-04-01 22:06:04       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-01 22:06:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-01 22:06:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-01 22:06:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-01 22:06:04       20 阅读

热门阅读

  1. 密码攻击

    2024-04-01 22:06:04       11 阅读
  2. SpringBoot单元测试

    2024-04-01 22:06:04       16 阅读
  3. 日常有疑惑的点总结

    2024-04-01 22:06:04       13 阅读
  4. Linux共享网络给其它主机

    2024-04-01 22:06:04       15 阅读
  5. FastAPI+React全栈开发13 FastAPI概述

    2024-04-01 22:06:04       12 阅读
  6. C# 字符串转json

    2024-04-01 22:06:04       18 阅读
  7. 医疗器械测试面试准备—质量部总监二面

    2024-04-01 22:06:04       30 阅读
  8. 蓝桥杯考前复习二

    2024-04-01 22:06:04       17 阅读
  9. 前端CSS样式(image)

    2024-04-01 22:06:04       18 阅读
  10. 2084: [蓝桥杯2023初赛] 整数删除

    2024-04-01 22:06:04       17 阅读