C++11 移动构造和完美转发
右值引用move()
int i=2; //i叫左值,"2"叫右值
int &b=i; //左值引用(给i所代表的内存块弄了个别名b)
int &&c=move(i) //右值引用(给i所代表的内存块弄了个新名字c)
//原来的名字i不能再被使用。
//int &&c 需要一个右值即常量,但右值引用过后,
//正常使用c又会被当成左值
std::move(i);
作用:将i这个左值转变为右值(即将变量i所占有的内容的所属权拿出来)
个人理解:将i所代表的内存块的所属权搬出来,给予了另一个变量,此时i就被掏空了,就不能再代表该内存块了------该内存块有了一个新名字。
右值引用优点: 这样赋值更快,性能更好,没有拷贝与复制和开辟新的内存块。
完美转发forward()
目的:转发右值
通过forward()函数的处理,原来是左值的还是左值,原来是右值的还是右值,没有变化,这样进一步保证了速度与性能。
std::forward<变量类型>(变量); //返回值跟变量性质一致(左值还是右值)
template<typename T>
void PerfectForward(T &&i) {
printValue(std::forward<T>(i)); //这个i会被当作右值处理
}
下面部分需要一定的操作系统基础,请谨慎食用!!!!
C++11 异步函数
头文件#include<future>
std::future 用于保存异步函数返回的值(共享状态)(获得了值就是共享状态就绪)
获得future 方法1(函数只能执行一次)
future<返回值类型>res = async(函数名,参数1,参数2……); //res就是一个有效的future对象。
该语句作用:将函数变为一个异步任务执行(即一个新的线程)
res.wait(); //会阻塞线程,直到res获得函数返回值
res.get(); //取出异步函数返回值,res由有效future变为无效
res.valid(); //判断 res是否有效,有效返回ture,无效返回false
res.wait_for(); //传入一段时间,阻塞线程
//直到获得值或超过这段时间
res.wait_until(); //传入一个时间点,阻塞线程
//直到获得值或超过该时间点
//async :创建有效future对象的方法1
future<int>res = async(launch::async,函数名,参数1,参数2……);
//launch::async 启动策略
//launch::async 创建时便开始执行后面的函数
//launch::deferred 暂时不执行,直到.wait()或.get()开始
//获取返回值时(即开始执行.wait()或.get()语句),才开始执行函数。
获得future方法2(函数可以多次执行)
packaged_task: 包装函数,便于异步调用
packaged_task<函数返回值(参数类型1……)>Bb(函数名);
//将函数包装为Bb
future<函数返回值类型>res=Bb.get_future();
//创建有效future对象的方法2
//.get_future() 会返回一个与Bb相关联的future对象
Bb.valid(); //判断对象Bb是否有共享状态----即获得函数返回值
Bb.make_ready_at_thread_exit();
//在线程退出时,才让共享状态就绪
Bb.reset(); //重置Bb,在关系不变的情况下,利用此可以多次调用函数
//每个packaged_task 只能调用一次.get_future();
例子:
int task(int i)
{
cout<<666<<endl;
return 0;
}
packaged_task<int(int)>pa(task);
future<int>fk=pa.get_future();
pa(1); //该语句执行等同于task(1); 并且返回值已经存入fk中
fk.get(); //获取函数返回值
C++11 线程库
头文件#include<thread>
创建线程
普通函数创建线程:
thread 线程名(函数地址,参数1,参数2……); //有参
thread 线程名(函数地址); //无参
成员函数构建线程:
thread 线程名(&类名::函数名,&对象名,参数1,参数2……);
线程名.join(); //等待对应线程执行结束,阻塞函数,释放资源
线程名.joinable(); //判断对应线程执行结束,已结束返回ture
线程名.detach(); //线程分离,不需要join()等待
线程名.get_id(); //获得对应线程的线程号,
//若在线程对象对应的线程函数中获取本线程id,
//调用"this_thread::get_id()"
线程函数的参数为值传递,即使引用也是对线程中的参数值的引用,与外部实参无关,若想通过线程实参来影响外部实参:
- 直接传参数地址
- 借助ref()函数----std::ref(实参)
- 通过lambda函数,利用[&]对外部实参进行引用。
互斥量库(mutex)
互斥锁
std::mutex 互斥锁名.lock(); //加锁(阻塞函数)
std::mutex 互斥锁名.unlock(); //解锁
std::mutex 互斥锁名.try_lock(); //互斥量未锁则加锁,已锁则返回false
递归互斥锁
std::recursive_mutex 递归互斥锁名.lock();
//在递归函数中使用递归互斥锁,其允许**同一个线程**
//对互斥量多次上锁,但释放时,需要调用同样多次的
//std::recursive_mutex 递归互斥锁名.unlock();
std::recursive_mutex 递归互斥锁名.try_lock();
//与互斥锁作用类似
定时锁
std::timed_mutex
std::timed_mutex 定时锁名.lock(); //加锁(阻塞函数)
std::timed_mutex 定时锁名.unlock(); //解锁
std::timed_mutex 定时锁名.try_lock();
//互斥量未锁则加锁,已锁则返回false
std::timed_mutex 定时锁名.try_lock_for();
//传入一个时间范围,该时间范围内未获得锁,就阻塞,
//若超过了该时间范围仍未获得锁,则返回false
std::timed_mutex 定时锁名.try_lock_untill();
//传入一个时间点,该时间点到来前未获得锁,就阻塞,
//若超过了该时间点仍未获得锁,则返回false
定时递归锁
std::recursive_timed_mutex
上面两者的结合。
智能锁(类似智能指针)
- lock_guard
创建: lock_guard<mutex>智能锁名(a); //a是一个mutex变量
lock_guard对象定义时,就开始自动上锁,直到该对象析构时,自动解锁,若只想锁住部分代码,则需要用代码块{……}。
- unique_lock 与lock_guard相同,但有更多的成员函数(用户可以自由控制加锁解锁)
创建: unique_lock<mutex>智能锁名(a); //a是一个mutex变量
智能锁名.lock(); //加锁(阻塞函数)
智能锁名.unlock(); //解锁
智能锁名.try_lock();
智能锁名.try_lock_for();
智能锁名.try_lock_untill();
智能锁名.release(); //释放对象所管理的互斥量指针,并释放所有权
智能锁名.owns_lock(); //判断当前对象是否上锁
智能锁名.mutex(); //返回对象所管理的互斥量指针
C++11 原子操作类型
线程对该类型变量访问时,自动进行互斥访问。(不需要互斥量)
原子操作类型定义需要大括号对其进行初始化。
普通类型: int 原子操作类型: atomic_int
普通类型: char 原子操作类型: atomic_char
普通类型: long 原子操作类型: atomic_long ……
也可以自定义原子操作类型: atomic<自定义类型>变量名
原子操作类型支持变量++,--,加一个值,减一个值,与,或,异或操作。
条件变量库(condition_variable)
头文件#include<condition_variable>
condition_variable中提供的成员函数,可分为wait系列和notify系列两类。
wait系列
wait系列成员函数的作用就是让调用线程进行阻塞等待,包括wait
、wait_for
和wait_until
。
//版本一
void wait(unique_lock<mutex>& lck);
//版本二
template<class T>
void wait(unique_lock<mutex>& lck, T pred);
- 调用第一个版本的wait函数时只需要传入一个互斥锁,线程调用wait后会立即被阻塞,直到被唤醒。
- 调用第二个版本的wait函数时除了需要传入一个互斥锁,还需要传入一个返回值类型为bool的可调用对象,与第一个版本的wait不同的是,当线程被唤醒后还需要调用传入的可调用对象-----一般是一个匿名函数,如果可调用对象的返回值为false,那么该线程还需要继续被阻塞。
为什么调用wait系列函数时需要传入一个互斥锁?
- 因为wait系列函数一般是在临界区中调用的,为了让当前线程调用wait阻塞时其他线程能够获取到锁,因此调用wait系列函数时需要传入一个互斥锁,当线程被阻塞时这个互斥锁会被自动解锁,而当这个线程被唤醒时,又会自动获得这个互斥锁。
- 因此wait系列函数实际上有两个功能,一个是让线程在条件不满足时进行阻塞等待,另一个是让线程将对应的互斥锁进行解锁。
wait_for和wait_until函数的使用方式与wait函数类似:
- wait_for函数也提供了两个版本的接口,只不过这两个版本的接口都比wait函数对应的接口多了一个参数,这个参数是一个时间段,表示让线程在该时间段内进行阻塞等待,如果超过这个时间段则线程被自动唤醒。
- wait_until函数也提供了两个版本的接口,只不过这两个版本的接口都比wait函数对应的接口多了一个参数,这个参数是一个具体的时间点,表示让线程在该时间点之前进行阻塞等待,如果超过这个时间点则线程被自动唤醒。
- 线程调用wait_for或wait_until函数在阻塞等待期间,其他线程调用notify系列函数也可以将该线程唤醒。此外,如果调用的是wait_for或wait_until函数的第二个版本的接口,那么当线程被唤醒后还需要调用传入的可调用对象-----一般是一个匿名函数,如果可调用对象的返回值为false,那么当前线程还需要继续被阻塞。
注意: 调用wait系列函数时,传入互斥锁的类型必须是unique_lock。
notify系列
notify系列成员函数的作用就是唤醒等待的线程,包括notify_one
和notify_all
。
notify_one
:唤醒等待队列中的首个线程,如果等待队列为空则什么也不做。notify_all
:唤醒等待队列中的所有线程,如果等待队列为空则什么也不做。
注意: 条件变量下可能会有多个线程在进行阻塞等待,这些线程会被放到一个等待队列中进行排队。