文章目录
🚩前言
在C++11起,标准规定了标注的并发库。头文件为#include <thread>
并发支持库 (C++11 起) - cppreference.com
其包含线程、原子操作、互斥、条件变量和 future 的内建支持。
而其中有一个namespace
为this_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。
标准库建议用稳定时钟度量时长。若实现用系统时间代替,则等待时间亦可能对时钟调节敏感。
异常
clock
、time_point
或 duration
在执行间抛出的任何异常(标准库提供的时钟、时间点和时长决不抛出)。
🏷️其他介绍
这里使用了一个RAII的技巧来测试计时
计时的方式比较多,这里采用的是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 之后的某个时间点。
异常
Clock
或 Duration
抛出的任何异常(标准库提供的时钟和时长决不抛出)。
🏷️其他介绍
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