(C++) this_thread 函数介绍

发布于:2024-04-25 ⋅ 阅读:(26) ⋅ 点赞:(0)

🚩前言

在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

#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介绍

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

返回当前线程的 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
# 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

#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;
}
Current thread ID: 24904
Current thread HANDLE: FFFFFFFE

mingw-w64

#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;
}
Current thread ID: 1

🕹️sleep_for<>()

std::this_thread::sleep_for - cppreference.com

🖥️Code

#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));
}
main : 2.004

🔖sleep_for介绍

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

#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");
}
startStamp : 1713801775996 ms
targetStamp : 1713801778996 ms
endStamp : 1713801779004 ms

🔖sleep_until介绍

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

#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介绍

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