c++多线程
🏅Thread
c++11 引入标准库std::thread,来扩展c++对多线程方面的支持
▶️构建函数
// ①
thread() noexcept;
// ②
thread( thread&& other ) noexcept;
// ③
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
// ④
thread( const thread& ) = delete;
▶️共有函数
get_id
std::thread::id get_id() const noexcept;
类似的std::this_thread命名空间内也提供了get_id()函数用于获取当前线程的线程ID
join && detach && joinable
join()函数用于阻塞主线程,等待子线程执行完毕去除阻塞作用,主线程再继续执行,子线程销毁随着主线程销毁而销毁
detach()函数用于从当前主线程分离出去,不会阻塞主线程,当主线程执行完销毁时子线程也随之销毁。
#include <iostream>
#include <thread>
#include <chrono> // 时间函数库
void fun1(int sleep_for,const char* name)
{
//当前线程沉睡sleep_for ms
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_for));
std::cout<<name<<"线程ID::"<<std::this_thread::get_id()<<"\n";
}
void fun2()
{
//当前线程沉睡100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout<<"fun2"<<"线程ID::"<<std::this_thread::get_id()<<"\n";
}
int main()
{
//主线程
std::thread t1(fun1,200,"fun1"); // 调用构造3
std::thread t2(fun2); // 调用构造2
auto res1 = t1.joinable(); //true
auto res2 = t2.joinable(); //true
/*
** 线程对象创建后如果指定任务函数,则线程开始执行,主线程和子线程建立连接
** 此时jionable()函数返回为true,如果该子线程进行了join()操作,则在join()
** 语句之前都是joinable的
*/
//join
t1.join(); //程序在此阻塞直到子线程t1执行完毕
auto res1 = t1.joinable(); //false
t2.detach(); //在此不阻塞,但子线程t2会随着主线程销毁而销毁,主线程不会等待t2执行完成,因此t2可能会没执行完就被销毁
auto res2 = t2.joinable(); //false
return 0;
}
operator= && hardware_concurrency
std::thread 重载了=操作符,但由于线程之间不能进行复制,只能进行资源的转移,因此std::thread之间的资源转移需要借助移动语义进行实现,函数原型如下:
thread& operator=(thread&& other) noexcept;
thread& operator=(thraed& other) = delete; //禁止复制拷贝
// thread t = std::move(t1) 资源转移从t1------>t
hardware_concurrency是std::thread提供的静态函数,用于查询cpu的核心数量
static unsigned hardware_concurrency() noexcept;
▶️std::call_once
call_once 函数保证一个函数在多线程环境下仅仅调用一次
// 定义于头文件 <mutex>
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
示例代码如下:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
once_flag g_flag;
void do_once(int a, string b)
{
cout << "name: " << b << ", age: " << a << endl;
}
void do_something(int age, string name)
{
static int num = 1;
call_once(g_flag, do_once, 19, "luffy");
cout << "do_something() function num = " << num++ << endl;
}
int main()
{
thread t1(do_something, 20, "ace");
thread t2(do_something, 20, "sabo");
thread t3(do_something, 19, "luffy");
t1.join();
t2.join();
t3.join();
return 0;
}
示例代码输出:
name: luffy, age: 19
do_something() function num = 1
do_something() function num = 2
do_something() function num = 3
🏅Mutex
std::mutex 定义于头文件
c++11中提供了四种互斥锁:
- std::mutex 独占的互斥锁,不能递归使用
- std::timed_mutex 带超时的独占互斥锁,不能递归使用
- std::recursive_mutex 递归互斥锁,不带超时
- std::recursive_timed_mutex 带超时的递归互斥锁
▶️成员函数
void lock();
bool try_lock();
void unlock();
lock():互斥锁有两种状态锁定和未锁定状态,当互斥锁处于未锁定状态时,调用lock函数获得锁的所有权并上锁,其他线程
得不到互斥锁的所有权而被lock函数阻塞;当拥有互斥锁所有权的线程解锁,此时其他线程会竞争互斥锁的所有权,竞争得到互斥锁所有权
的线程继续执行,没有得到所有权的线程则继续被lock函数阻塞
try_lock:与lock函数作用类似,但与lock函数不同的是:try_lock不会阻塞线程
- 如果互斥锁是未锁定状态,获取互斥锁所有权上锁成功返回true
- 如果互斥锁是锁定状态,无法得到互斥锁所有权返回false
▶️线程同步
线程同步思路:
- 找到多个线程操作的共享资源(全局变量、堆内存)
- 找到操作共享资源的上下文即临界区
- 在临界区上边界调用lock函数
- 在临界区下边界调用unlock函数
NOTE:线程同步将多线程并行访问
std::atomic
c++ atomic 保证共享变量的操作不受其他线程的影响,避免出现竞态条件和死锁,是c++中一种同步机制
condition_variable
condition_variable是c++ 11提供的线程同步机制,它阻塞一个线程,直到收到另一个线程的通知或者信号,条件变量需要和互斥量配合使用,c++11提供了2种条件变量:
- condition_variable : 需要配合std::unique_lockstd::mutex使用
代码示例
#include<iostream>
#include <mutex>
#include <condition_variable>
#include <string>
#include <thread>
std::condition_variable cv;
std::mutex lk;
🏅Chrono时间库
命名空间:std::chrono,常见预定义类型:
- std::chrono::hours 小时
- std::chrono::seconds 秒
- std::chrono::minutes 分钟
- std::chrono::milliseconds 毫秒
- std::chrono::microseconds 微秒
- std::chrono::nanoseconds 纳秒
时间间隔duration
duration表示一段时间间隔,原型定义如下:
//定义于头文件 <chrono>
template<class Rep, class Period = std::ratio<1>> duration;
- Rep:这是一个数值类型,表示时钟数(周期)的类型(默认为整形)。若 Rep 是浮点数,则 duration 能使用小数描述时钟周期的数目
- Period:表示时钟得周期,原型定义如下:
// 定义于头文件 <ratio>
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;
ratio类表示每个时钟周期的秒数,其中第一个模板参数 Num代表分子,Denom代表分母,该分母值默认为 1。因此,ratio代表的是一个分子除以分母的数值
- ratio<2> 代表一个时钟周期是 2 秒
- ratio<60 > 代表一分钟
- ratio<60*60 > 代表一个小时
- ratio<606024 > 代表一天
- ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒
- ratio<1,1000000 > 代表一微秒,ratio<1,1000000000 > 代表一纳秒
duration的构建原型如下
// 1. 拷贝构造函数
duration( const duration& ) = default;
// 2. 通过指定时钟周期的类型来构造对象
template< class Rep2 >
constexpr explicit duration( const Rep2& r );
// 3. 通过指定时钟周期类型,和时钟周期长度来构造对象
template< class Rep2, class Period2 >
constexpr duration( const duration<Rep2,Period2>& d );
duration提供了获取始终周期数的函数,定义如下:
constexpr rep count() const;
通过构建函数构造时间间隔对象示例如下:
#include <chrono>
#include <iostream>
using namespace std;
int main()
{
chrono::hours h(1); // 一小时
chrono::milliseconds ms{ 3 }; // 3 毫秒
chrono::duration<int, ratio<1000>> ks(3); // 3000 秒
// chrono::duration<int, ratio<1000>> d3(3.5); // error
chrono::duration<double> dd(6.6); // 6.6 秒
// 使用小数表示时钟周期的次数
chrono::duration<double, std::ratio<1, 30>> hz(3.5); //3.5*(1/30)秒
}
具体使用代码示例如下:
#include <chrono>
#include <iostream>
int main()
{
std::chrono::milliseconds ms{3}; // 3 毫秒
std::chrono::microseconds us = 2*ms; // 6000 微秒
// 时间间隔周期为 1/30 秒
std::chrono::duration<double, std::ratio<1, 30>> hz(3.5);
//操作符重载
std::chrono::minutes t1(10);
std::chrono::seconds t2(60);
std::chrono::seconds t3 = t1 - t2; // type::minutes - type::seconds --> type::seconds
std::cout<< t3.count()<<std::endl;
/*
**注意事项:duration 的加减运算有一定的规则,当两个 duration 时钟周期不相同的时候,
**会先统一成一种时钟,然后再进行算术运算,
**统一的规则如下:假设有 ratio<x1,y1> 和 ratio<x2,y2 > 两个时钟周期,
**首先需要求出 x1,x2 的最大公约数 X,然后求出 y1,y2 的最小公倍数 Y,
**统一之后的时钟周期 ratio 为 ratio<X,Y>。
*/
std::chrono::duration<double,std::ratio<9,7>> d1(3);
std::chrono::duration<double,std::ratio<6,5>> d2(1);
std::chrono::duration<double,std::ratio<3,35>> d3 = d1 - d2;
//对于分子6、9的最大公约数为3,分母7、5的最小公倍数35,因此推导出d3的ratio为std::ratio<3,35>
std::cout << "3 ms duration has " << ms.count() << " ticks\n"
<< "6000 us duration has " << us.count() << " ticks\n"
<< "3.5 hz duration has " << hz.count() << " ticks\n";
}
时间点time point
定义原型如下:
// 定义于头文件 <chrono>
template<
class Clock,
class Duration = typename Clock::duration
> class time_point;
构建函数如下:
// 1. 构造一个以新纪元(epoch,即:1970.1.1)作为值的对象,需要和时钟类一起使用,不能单独使用该无参构造函数
time_point();
// 2. 构造一个对象,表示一个时间点,其中d的持续时间从epoch开始,需要和时钟类一起使用,不能单独使用该构造函数
explicit time_point( const duration& d );
// 3. 拷贝构造函数,构造与t相同时间点的对象,使用的时候需要指定模板参数
template< class Duration2 >
time_point( const time_point<Clock,Duration2>& t);
时钟clocks
chrono中提供了获取当前系统时钟的时钟类,包含的时钟一共有三类:
- system_clock:系统时钟,系统的时钟可以修改,甚至可以网络对时,因此基于系统时钟计算的时间间隔差可能不准
- steday_clock:固定时钟,相当于秒表,开始计时后,时间只能增加而不能减少,适合计算程序运行时间
- high_resolution_clock:和steday_clock等价
system_clock
函数源码定义如下:
struct system_clock { // wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
using rep = long long;
using period = ratio<1, 10'000'000>; // 100 nanoseconds
using duration = chrono::duration<rep, period>;
using time_point = chrono::time_point<system_clock>;
static constexpr bool is_steady = false;
//返回当前时间点
_NODISCARD static time_point now() noexcept
{ // get current time
return time_point(duration(_Xtime_get_ticks()));
}
_NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept
{ // convert to __time64_t
return duration_cast<seconds>(_Time.time_since_epoch()).count();
}
_NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept
{ // convert from __time64_t
return time_point{seconds{_Tm}};
}
};
示例使用代码如下:
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
// 新纪元1970.1.1时间
system_clock::time_point epoch;
duration<int, ratio<60*60*24>> day(1);
// 新纪元1970.1.1时间 + 1天
system_clock::time_point ppt(day);
using dday = duration<int, ratio<60 * 60 * 24>>;
// 新纪元1970.1.1时间 + 10天
time_point<system_clock, dday> t(dday(10));
// 系统当前时间
system_clock::time_point today = system_clock::now();
// 转换为time_t时间类型
time_t tm = system_clock::to_time_t(today);
cout << "今天的日期是: " << ctime(&tm);
time_t tm1 = system_clock::to_time_t(today+day);
cout << "明天的日期是: " << ctime(&tm1);
time_t tm2 = system_clock::to_time_t(epoch);
cout << "新纪元时间: " << ctime(&tm2);
time_t tm3 = system_clock::to_time_t(ppt);
cout << "新纪元时间+1天: " << ctime(&tm3);
time_t tm4 = system_clock::to_time_t(t);
cout << "新纪元时间+10天: " << ctime(&tm4);
}
示例代码输出为:
今天的日期是: Thu Apr 8 11:09:49 2021
明天的日期是: Fri Apr 9 11:09:49 2021
新纪元时间: Thu Jan 1 08:00:00 1970
新纪元时间+1天: Fri Jan 2 08:00:00 1970
新纪元时间+10天: Sun Jan 11 08:00:00 1970
steday_clock
源码定义如下所示:
struct steady_clock { // wraps QueryPerformanceCounter
using rep = long long;
using period = nano;
using duration = nanoseconds;
using time_point = chrono::time_point<steady_clock>;
static constexpr bool is_steady = true;
// get current time
_NODISCARD static time_point now() noexcept
{
// doesn't change after system boot
const long long _Freq = _Query_perf_frequency();
const long long _Ctr = _Query_perf_counter();
static_assert(period::num == 1, "This assumes period::num == 1.");
const long long _Whole = (_Ctr / _Freq) * period::den;
const long long _Part = (_Ctr % _Freq) * period::den / _Freq;
return time_point(duration(_Whole + _Part));
}
};
示例代码求函数的运行时间耗时:
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
// 获取开始时间点
steady_clock::time_point start = steady_clock::now();
// 执行业务流程
cout << "print 1000 stars ...." << endl;
for (int i = 0; i < 1000; ++i)
{
cout << "*";
}
cout << endl;
// 获取结束时间点
steady_clock::time_point last = steady_clock::now();
// 计算差值
auto dt = last - start;
cout << "总共耗时: " << dt.count() << "纳秒" << endl;
}
转换函数 duration_cast
duration_cast是chrono提供的修改duration内部的时钟周期priod和周期次数rep,该函数原型如下:
template <class ToDuration, class Rep, class Period>
constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);
- 如果对时钟周期进行修改,则源时钟周期必须能整除目标时钟周期(比如小时到分钟)
- 如果对周期次数进行修改,窄字符宽度默认可以向宽字符精度转换(int转到float/double)
- 如果时钟周期和周期次数都改变,则根据第二点来推导
- 如果以上都不满足,需要利用duration_cast进行转换
示例代码如下:
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
void f()
{
cout << "print 1000 stars ...." << endl;
for (int i = 0; i < 1000; ++i)
{
cout << "*";
}
cout << endl;
}
int main()
{
auto t1 = steady_clock::now();
f();
auto t2 = steady_clock::now();
// 整数时长:时钟周期纳秒转毫秒,要求 duration_cast
auto int_ms = duration_cast<chrono::milliseconds>(t2 - t1);
// 小数时长:不要求 duration_cast
duration<double, ratio<1, 1000>> fp_ms = t2 - t1;
cout << "f() took " << fp_ms.count() << " ms, "
<< "or " << int_ms.count() << " whole milliseconds\n";
}
示例代码输出:
print 1000 stars ....
****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
f() took 40.2547 ms, or 40 whole milliseconds
本文来自博客园,作者:DumoIO,转载请注明原文链接:https://www.cnblogs.com/dumoio/p/17627063.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!