c++11扩展(c++11并发库)
文章目录
- 1、并发支持库
-
- 1.1 thread库
- 2. this_thread
- 3. mutex
- 4. lock_guard
- 5. unique_lock
- 6. lock和try_lock
- 7. call_once
- 8. atomic
- 9. condition_variable
- 总结
1、并发支持库
1.1 thread库
a、thread库文档https://legacy.cplusplus.com/reference/thread/thread/thread/
b、thread库是对各个系统的线程库进行封装,如Linux下的pthera库和windows下Thread库等,所以c++11thread库第一个特点:可以跨平台,第二个特点:Linux和windows下提供的线程库是面向过程的,c++11thread是库面向对象的,并且融入了c++11语言特点,如右值引用的移动语义,可变模版参数等。
c、下⾯线程创建这⾥有4个构造函数,⽇常最常⽤的是第2个,他⽀持传⼀个可调⽤对象和参数即可,相⽐pthread_create⽽⾔,这⾥不再局限于只传递函数指针,其次就是参数传递也更⽅便,pthread_create调⽤时,要传递多个参数需要打包成⼀个结构体,传结构体对象的指针过去。
d、 另外也可以⽤第1个和第4个配合来创建线程,我们可以把右值线程对象移动构造或者移动赋值给另⼀个线程对象。
e、 第3个可以看到线程对象是不⽀持拷⻉的。
f、 join是主线程结束前需要阻塞等待创建的从线程,否则主线程结束,进程就结束了,从线程可能还在运⾏就被强⾏终⽌了。
j、 class thread::id是⼀个thread的内部类⽤来表⽰线程id,⽀持⽐较⼤⼩,流插⼊和提取,通过特化hash仿函数做unordered_map和unordered_set的id等。底层的⻆度看thread本质还是封装各个平台的线程库接⼝。各个平台的线程id表⽰类型不同,所以只能⽤⼀个类来进⾏封装。线程对象可以通过get_id获取线程id,在执⾏体内可以通过this_thread::get_id()获取线程id。
default (1)
thread() noexcept;
initialization (2)
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
copy [deleted] (3)
thread (const thread&) = delete;
copy [deleted] (3)
thread& operator= (const thread&) = delete;
move (4)
thread (thread&& x) noexcept;
move (4)
thread& operator= (thread&& rhs) noexcept;
// pthread库
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg);
// windows线程创建API
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)
void join();#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
using namespace std;
void Print(int n, int i)
{
for (; i < n; i++)
{
cout << this_thread::get_id() <<":" << i << endl;//调用全局函数,获取当前函数的id
}
cout << endl;
}
//多核
int main()
{
thread t1(Print, 10, 0);
thread t2(Print, 20, 10);
// 获取线程id
//cout << t1.get_id() << endl;
//cout << t2.get_id() << endl;
t1.join();//从线程结束
t2.join();
// 获取当前运⾏线程id
cout << this_thread::get_id() << endl;
return 0;
}
2. this_thread
this_thread官方文档
• this_thread是⼀个命名空间,主要封装了线程相关的4个全局接⼝函数。
• get_id是当前执⾏线程的线程id。
• yield是主动让出当前线程的执⾏权,让其他线程先执⾏。此函数的确切⾏为依赖于实现,特别是取决于使⽤中的 OS 调度器机制和系统状态。例如,先进先出实时调度器(Linux 的 SCHED_FIFO)会挂起当前线程并将它放到准备运⾏的同优先级线程的队列尾,⽽若⽆其他线程在同优先级,则 yield ⽆效果。
• sleep_for阻塞当前线程执⾏,⾄少 经过指定的 sleep_duration。因为调度或资源争议延迟,此函数可能阻塞⻓于 sleep_duration。
• sleep_until阻塞当前线程的执⾏,直⾄抵达指定的 sleep_time。函数可能会因为调度或资源纠纷延迟⽽阻塞到 sleep_time 之后的某个时间点。
https://legacy.cplusplus.com/reference/chrono/ chrono是⼀个计时相关的类型。
https://legacy.cplusplus.com/reference/chrono/duration/是⽤来管理⼀个相对时间段的类。
https://legacy.cplusplus.com/reference/chrono/time_point/是⽤来管理⼀个绝对时间点的类。
template <class Clock, class Duration>
void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);
template <class Rep, class Period>
void sleep_for (const chrono::duration<Rep,Period>& rel_time);
// this_thread::sleep_for example
#include <iostream> // std::cout, std::endl
#include <thread> // std::this_thread::sleep_for
#include <chrono> // std::chrono::seconds
int main()
{
std::cout << "countdown:\n";
for (int i=10; i>0; --i) {
std::cout << i << std::endl;
std::this_thread::sleep_for (std::chrono::seconds(1));//休眠1秒
}
std::cout << "Lift off!\n";
return 0;
}
// this_thread::sleep_for example
#include <iostream> // std::cout
#include <iomanip> // std::put_time
#include <thread> // std::this_thread::sleep_until
#include <chrono> // std::chrono::system_clock
#include <ctime> // std::time_t, std::tm, std::localtime, std::mktime
int main()
{
using std::chrono::system_clock;
std::time_t tt = system_clock::to_time_t (system_clock::now());
struct std::tm * ptm = std::localtime(&tt);
std::cout << "Current time: " << std::put_time(ptm,"%X") << '\n';
std::cout << "Waiting for the next minute to begin...\n";
++ptm->tm_min; ptm->tm_sec=0;
std::this_thread::sleep_until (system_clock::from_time_t (mktime(ptm)));
std::cout << std::put_time(ptm,"%X") << " reached!\n";
return 0;
}
int main ()
{
using namespace std::chrono;
duration<int,std::ratio<60*60*24> > one_day (1);
system_clock::time_point today = system_clock::now();
system_clock::time_point tomorrow = today + one_day;
time_t tt;
tt = system_clock::to_time_t ( today );
std::cout << "today is: " << ctime(&tt);
tt = system_clock::to_time_t ( tomorrow );
std::cout << "tomorrow will be: " << ctime(&tt);
return 0;
}
3. mutex
https://legacy.cplusplus.com/reference/mutex/
• mutex是封装的互斥锁的类,⽤于保护临界区的共享数据。mutex主要提供lock和unlock两个接⼝函数。 mutex 提供排他性⾮递归所有权语义:(1)调⽤⽅线程从它成功调⽤ lock或 try_lock 开始,到它调⽤ unlock 为⽌占有 mutex。(2)线程占有 mutex 时,其他线程如果试图要求 mutex 的所有权,那么就会阻塞(对于 lock 的调⽤), 对于 try_lock就会返回false 。
锁不能被拷贝,因为锁万一里面有线程就会拷不到。
• 如果 mutex 在仍为任何线程所占有时即被销毁,或在占有 mutex 时线程终⽌,那么⾏为未定义。
• ⽰例1代码展⽰了mutex的使⽤,其实如果线程对象传参给可调⽤对象时,使⽤引⽤⽅式传参,实参位置需要加上ref(obj)的⽅式,主要原因是thread本质还是系统库提供的线程API的封装,thread构造取到参数包以后,要调⽤创建线程的API,还是需要将参数包打包成⼀个结构体传参过去,那么打包成结构体时,参考包对象就会拷⻉给结构体对象,使⽤ref传参的参数,会让结构体中的对应参数成员类型推导为引⽤,这样才能实现引⽤传参,⽰例2代码截取了vs2019下thread库中的部分源码帮助理解。https://legacy.cplusplus.com/reference/functional/ref/?kw=ref
• time_mutex跟mutex完全类似,只是额外提供try_lock_for和try_lock_untile的接⼝,这两个接⼝跟try_lock类似,只是他不会⻢上返回,⽽是直接进⼊阻塞,直到时间条件到了或者解锁了就会唤醒试图获取锁资源。
• recursive_mutex 跟mutex完全类似,recursive_mutex 提供排他性递归所有权语义:
(1)调⽤⽅线程在从它成功调⽤ lock 或 try_lock 开始的时期⾥占有 recursive_mutex。此时期之内,线程可以进⾏对 lock 或 try_lock 的附加调⽤。所有权的时期在线程进⾏匹配次数的 unlock 调⽤时结束。
(2)线程占有 recursive_mutex 时,若其他所有线程试图要求 recursive_mutex 的所有权,则它们将阻塞(对于调⽤ lock)或收到 false 返回值(对于调⽤ try_lock)
// ⽰例1:
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;
void Print(int n, int& rx, mutex& rmtx)
{
//锁放在外面,效率高,因为是t1全部走完了,才是t2走
//锁放在循环里面,效率低,会发生阻塞轮替,产生消耗。
rmtx.lock();
for (int i = 0; i < n; i++)
{
// t1 t2
++rx;
}
rmtx.unlock();
}
int main()
{
int x = 0;
mutex mtx;
// 这⾥必须要⽤ref()传参,现成中拿到的才是x和mtx的引⽤,具体原因需要看下⾯thread源码中的分析
// https://legacy.cplusplus.com/reference/functional/ref/?kw=ref
thread t1(Print, 1000000, ref(x), ref(mtx));
thread t2(Print, 2000000, ref(x), ref(mtx));
t1.join();
t2.join();
cout << x << endl;
return 0;
}
int main()
{
int x = 0;
mutex mtx;
// 将上⾯的代码改成使⽤lambda捕获外层的对象,也就可以不⽤传参数,间接解决了上⾯的问题
auto Print = [&x, &mtx](size_t n) {
mtx.lock();
for (size_t i = 0; i < n; i++)
{
++x;
}
mtx.unlock();
};
thread t1(Print, 1000000);
thread t2(Print,