(C++) this_thread 函数介绍

文章目录

🚩前言

在C++11起,标准规定了标注的并发库。头文件为#include <thread> 并发支持库 (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

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>

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();
        }
    }
}

🔖get_id介绍

cpp 复制代码
std::thread::id get_id() noexcept;

返回当前线程的 id

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

shell 复制代码
# 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
cpp 复制代码
# 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

🏷️其他介绍

C语言获取线程id或句柄

msvc-x64

c 复制代码
#include <stdio.h>
#include <windows.h>

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;
}
shell 复制代码
Current thread ID: 24904
Current thread HANDLE: FFFFFFFE

mingw-w64

c 复制代码
#include <pthread.h>
#include <stdio.h>

int main() {
    pthread_t thread_id = pthread_self();
    printf("Current thread ID: %ld\n", (long)thread_id);
    return 0;
}
shell 复制代码
Current thread ID: 1

🕹️sleep_for<>()

std::this_thread::sleep_for - cppreference.com

🖥️Code

cpp 复制代码
#include <chrono>
#include <ctime>
#include <iostream>
#include <thread>

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));
}
shell 复制代码
main : 2.004

🔖sleep_for介绍

cpp 复制代码
template< class Rep, class Period >
void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration );

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

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

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

异常

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

🏷️其他介绍

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

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

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

而C++也有增强的<chrono>库。

🕹️sleep_until<>()

std::this_thread::sleep_until - cppreference.com

🖥️Code

cpp 复制代码
#include <chrono>
#include <iostream>
#include <string>
#include <thread>

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");
}
shell 复制代码
startStamp : 1713801775996 ms
targetStamp : 1713801778996 ms
endStamp : 1713801779004 ms

🔖sleep_until介绍

cpp 复制代码
template< class Clock, class Duration >
void sleep_until( const std::chrono::time_point<Clock, Duration>& sleep_time );

阻塞当前线程的执行,直至抵达指定的 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

cpp 复制代码
#include <atomic>
#include <iostream>
#include <thread>

/**
 * 简单实现自旋锁
 */
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;
}

🔖yield介绍

cpp 复制代码
void yield() noexcept;

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

注意

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

🏷️其他介绍

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

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

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

std::atomic_flag - cppreference.com

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

(构造函数) 构造 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

相关推荐
猫头虎7 分钟前
如何实现在多跳UDP传输场景,保证单文件和多文件完整传输的成功率?
java·开发语言·网络·python·网络协议·golang·udp
小白的代码日记9 分钟前
Linux常用指令
linux·运维·服务器
月舞之剑27 分钟前
linux离线安装nodejs
linux·node.js
维尔切43 分钟前
Linux中Https配置与私有CA部署指南
linux·运维·https
小熊h1 小时前
【自动化备份全网服务器数据项目】
linux·服务器·自动化·备份数据
SimonKing1 小时前
深入理解HanLP1.x,填平可能遇到的坑
java·后端·程序员
尘心不灭1 小时前
MyBatis 缓存与 Spring 事务相关笔记
java·spring·mybatis
watson_pillow1 小时前
mfc按钮点击事件没有触发,且程序卡死
c++·mfc
Java中文社群1 小时前
说说内存泄漏的常见场景和排查方案?
java·后端·面试
_Kayo_2 小时前
JS深拷贝 浅拷贝、CSS垂直水平居中
开发语言·前端·javascript