• (C++) this_thread 函数介绍


    🚩前言

    在C++11起,标准规定了标注的并发库。头文件为#include 并发支持库 (C++11 起) - cppreference.com

    其包含线程、原子操作、互斥、条件变量和 future 的内建支持。

    而其中有一个namespacethis_thread,里面有四个重要的全局函数,其实现都与当前系统环境和编译器强绑定。

    std::this_thread 符号索引 - cppreference.com

    ⭐std::this_thread

    🕹️get_id()

    std::this_thread::get_id - cppreference.com

    🖥️Code

    #include 
    #include 
    #include 
    
    void show_thread_id(std::string msg) {
        std::cout << msg << " = " << std::this_thread::get_id() << std::endl;
    }
    
    int main() {
        auto id = std::this_thread::get_id();
        std::cout << "std::this_thread::get_id() = " << typeid(id).name() << std::endl;
        show_thread_id("main");
    
        for (int i = 0; i < 3; i += 1) {
            std::thread th(show_thread_id, "son-thread");
            if (th.joinable()) {
                th.join();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    🔖get_id介绍

    std::thread::id get_id() noexcept;
    
    • 1

    返回当前线程的 id

    这个id是一个std::thread的内部类型std::thread::id。其实现依附于所在平台。

    # msvc-x64
    std::this_thread::get_id() = class std::thread::id
    main = 13232
    son-thread = 3908
    son-thread = 3724
    son-thread = 4840
    
    # mingw-w64
    std::this_thread::get_id() = NSt6thread2idE
    main = 1
    son-thread = 2
    son-thread = 3
    son-thread = 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    # msvc-x64
    std::thread::id::_Thrd_id_t _Id;
    using _Thrd_id_t = unsigned int;
    
    # mingw-w64
    std::thread::id::native_handle_type _M_thread;
    using native_handle_type = __gthread_t;
    typedef pthread_t __gthread_t;
    typedef uintptr_t pthread_t;
    __MINGW_EXTENSION typedef unsigned __int64 uintptr_t;
    #define __int64 long long
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    🏷️其他介绍

    C语言获取线程id或句柄

    msvc-x64

    #include 
    #include 
    
    int main() {
        DWORD thread_id = GetCurrentThreadId();
        printf("Current thread ID: %lu\n", thread_id);
    
        HANDLE thread_handle = GetCurrentThread();
        printf("Current thread HANDLE: %p\n", thread_handle);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    Current thread ID: 24904
    Current thread HANDLE: FFFFFFFE
    
    • 1
    • 2

    mingw-w64

    #include 
    #include 
    
    int main() {
        pthread_t thread_id = pthread_self();
        printf("Current thread ID: %ld\n", (long)thread_id);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    Current thread ID: 1
    
    • 1

    🕹️sleep_for<>()

    std::this_thread::sleep_for - cppreference.com

    🖥️Code

    #include 
    #include 
    #include 
    #include 
    
    class Timer {
    private:
        std::string  hint;
        std::clock_t curTime = 0;
    
    public:
        Timer(const std::string& str = "") : hint(str) {
            curTime = std::clock();
        }
    
        ~Timer() {
            std::clock_t endTime = std::clock();
            std::cout << hint << " : ";
            std::cout << 1.0 * (endTime - curTime) / 1000 << std::endl;
        }
    };
    
    int main() {
        Timer timer("main");
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    main : 2.004
    
    • 1

    🔖sleep_for介绍

    template< class Rep, class Period >
    void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration );
    
    • 1
    • 2

    阻塞当前线程执行,至少 经过指定的 sleep_duration。

    因为调度或资源争议延迟,此函数可能阻塞长于 sleep_duration。

    标准库建议用稳定时钟度量时长。若实现用系统时间代替,则等待时间亦可能对时钟调节敏感。

    异常

    clocktime_pointduration 在执行间抛出的任何异常(标准库提供的时钟、时间点和时长决不抛出)。

    🏷️其他介绍

    这里使用了一个RAII的技巧来测试计时

    (C++) 基于RAII的简单计时器_哔哩哔哩_bilibili

    计时的方式比较多,这里采用的是C语言的库。(C语言) time库-日期和时间工具 -CSDN博客

    而C++也有增强的库。

    🕹️sleep_until<>()

    std::this_thread::sleep_until - cppreference.com

    🖥️Code

    #include 
    #include 
    #include 
    #include 
    
    using chrono_time_point = std::chrono::high_resolution_clock::time_point;
    using chrono_ms         = std::chrono::milliseconds;
    
    void show_time_point(chrono_time_point point, std::string msg) {
        intmax_t ns = std::chrono::duration_cast<chrono_ms>(point.time_since_epoch()).count();
        std::cout << msg << " : " << ns << " ms" << std::endl;
    }
    
    int main() {
        chrono_time_point startStamp  = std::chrono::high_resolution_clock::now();
        chrono_time_point targetStamp = startStamp + std::chrono::seconds(3);
    
        // 设置延时的目标时间
        std::this_thread::sleep_until(targetStamp);
        chrono_time_point endStamp = std::chrono::high_resolution_clock::now();
    
        show_time_point(startStamp, "startStamp");
        show_time_point(targetStamp, "targetStamp");
        show_time_point(endStamp, "endStamp");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    startStamp : 1713801775996 ms
    targetStamp : 1713801778996 ms
    endStamp : 1713801779004 ms
    
    • 1
    • 2
    • 3

    🔖sleep_until介绍

    template< class Clock, class Duration >
    void sleep_until( const std::chrono::time_point<Clock, Duration>& sleep_time );
    
    • 1
    • 2

    阻塞当前线程的执行,直至抵达指定的 sleep_time。

    Clock 必须符合时钟 (Clock) 要求。如果 std::chrono::is_clock_v 是 false,那么程序非良构。 (C++20 起)

    标准推荐使用绑定到 sleep_time 的时钟,此时调整时钟会有影响。因此,阻塞的时长可能会小于或大于调用时的 sleep_time - Clock::now(),这取决于调整的方向以及实现是否尊重这样的调整。函数也可能会因为调度或资源纠纷延迟而阻塞到 sleep_time 之后的某个时间点。

    异常

    ClockDuration 抛出的任何异常(标准库提供的时钟和时长决不抛出)。

    🏷️其他介绍

    std::chrono::time_point的实际类型也是基于实际实现的。

    而其重载了 operator +()可以与std::duration<>进行运算,因此其对时间的运算更加的自由。

    而重新转换又需要使用std::chrono::duration_cast<>来处理,

    最后的count()返回值取决于duration<_Rep, _Period>::_Rep

    而其在实现层面,一般默认会使用整形能获取的最大值,

    一般使用intmax_t

    上面代码为了展现实际的数据类型而全部写了出来,实际编程中这些太冗余了。

    局部变量的话建议直接写auto,跨范围的用using规定一个别名。

    🕹️yield()

    std::this_thread::yield - cppreference.com

    🖥️Code

    #include 
    #include 
    #include 
    
    /**
     * 简单实现自旋锁
     */
    struct SpinLock {
        std::atomic_flag flag = {ATOMIC_FLAG_INIT};
    
        void lock() {
            // 循环自旋
            while (flag.test_and_set(std::memory_order_acquire)) {
                // 自旋的时候让出调度权,提升cpu效率
                std::this_thread::yield();
            }
        }
    
        void unlock() {
            flag.clear(std::memory_order_release);
        }
    };
    
    SpinLock spinlock;
    int      x = 0;
    
    void thread_func() {
        for (int i = 0; i < 100000; i += 1) {
            spinlock.lock();
            x += 1;
            spinlock.unlock();
        }
    }
    
    int main() {
        std::thread th1(thread_func);
        std::thread th2(thread_func);
    
        th1.join();
        th2.join();
    
        std::cout << x << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    🔖yield介绍

    void yield() noexcept;
    
    • 1

    向实现提供一个提示,重新调度线程的执行以允许其他线程运行。

    注意

    此函数的确切行为依赖于实现,特别是取决于使用中的 OS 调度器机制和系统状态。例如,先进先出实时调度器(Linux 的 SCHED_FIFO)会挂起当前线程并将它放到准备运行的同优先级线程的队列尾(而若无其他线程在同优先级,则 yield 无效果)。

    🏷️其他介绍

    yield()与当前系统的任务调度策略强依赖。

    上述代码是一个自旋锁的简单实现,注意在实践中自旋锁通常是错误

    就是通过原子的读改写达到阻塞进入临界区的作用。

    std::atomic_flag - cppreference.com

    std::atomic_flag是C++11中的一个原子布尔类型,与std::atomic不同,它保证了是免锁的。

    (构造函数)构造 atomic_flag (公开成员函数)
    operator=赋值运算符 (公开成员函数)
    clear原子地设置标志为 false (公开成员函数)
    test_and_set原子地设置标志为 true 并获得其先前值 (公开成员函数)
    test(C++20)原子地返回标志的值 (公开成员函数)
    wait(C++20)阻塞线程直至被提醒且原子值更改 (公开成员函数)
    notify_one(C++20)提醒至少一个在原子对象上的等待中阻塞的线程 (公开成员函数)
    notify_all(C++20)提醒所有在原子对象上的等待中阻塞的线程 (公开成员函数)

    可以见得,在C++20中std::atomic_flag能够达到通知阻塞的作用,这极大的可以优化有传统条件变量的通知。

    能够有更小的开销,对一些库的性能提升非常大。

    🚩END

    关注我,学习更多C/C++,算法,计算机知识

    B站:

    👨‍💻主页:天赐细莲 bilibili

  • 相关阅读:
    GNU和Linux的关系、 Linux的发行版本、CentOs和RedHat的区别
    【Azure API 管理】Azure APIM服务集成在内部虚拟网络后,在内部环境中打开APIM门户使用APIs中的TEST功能失败
    基于JavaSwing开发单位固定资产登记管理系统 毕业设计 课程设计 大作业
    华为机试真题 C++ 实现【迷宫问题】
    muduo库的高性能日志库(四)——LogFile文件
    SCR截面速度、氨氮比等标准及相对标准偏差计算
    栈(顺序栈)实现Language C
    Game Maker 基金会呈献:归属之谷
    vue 创建vue项目
    备忘录软件综合评测:优点、缺点、评价及替代品
  • 原文地址:https://blog.csdn.net/CUBE_lotus/article/details/138168694