互斥量:
std::mutex:独占的互斥量,不能递归使用
std::timed_mutex:带超时的独占互斥量,不能递归使用
std::recursive_mutex:递归互斥量,不带超时功能
std::recursive_timed_mutex:带超时的递归互斥量
这些互斥量的基本接口很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量的所有权,在线程获得互斥量并完成任务后,必须使用unlock()来解除对互斥量的占用,lock()和unlock()必须成对出现,try_lock()尝试锁定互斥量,成功返回true,失败返回false,是非阻塞的。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex g_lock;
void func()
{
g_lock.lock();
std::cout<<"enter thread"<<std::this_thread::get_id()<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout<<"leave thread"<<std::this_thread::get_id()<<std::endl;
g_lock.ulock();
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
t1.join();
t2.join();
t3.join();
return 0;
}
使用lock_guard可以简化lock/ulock的使用,更加安全,其在构造时会自动锁定互斥量,在退出作用域后会自动进行析构,其使用见上面注释部分。
递归互斥量std::recursive_mutex,允许同一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时的死锁问题,但是尽量避免使用该锁,原因如下:
* 使用该锁本身是可以简化的,使用该锁容易引起更复杂逻辑的产生
* 递归锁比起非递归锁,效率更低
* 递归锁在一个线程内多次获得同一个互斥量,最大数并没有具体说明
带超时的互斥量std::timed_mutex,在获取锁的时候增加超时等待功能,即设置获取互斥锁的等待时间,可以使用一个while循环来不断的获取该互斥量,其相比std::mutex()多了两个超时获取锁的接口:try_lock_for()和try_lock_until()。
std::timed_mutex mutex;
void work()
{
std::chrono::milliseconds timeout(100);
while(true){
if(mutex.try_lock_for(timeout)){
}
}
}
条件变量:能阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时,才唤醒当前阻塞的线程,条件变量需要和互斥量配合起来使用。
1、condition_variable 配合std::unique_lock<std::mutex>进行wait的操作;
2、condition_variable_any和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable差一些;
其使用过程如下:
a、拥有条件变量的线程获取互斥量;
b、循环检查某个条件,如果条件不满足则阻塞直到条件满足,然后向下执行;
c、某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或所有的等待线程;
以下例子为利用条件变量实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取,比如半同步半异步线程池的同步队列
#include <mutex>
#include <thread>
#include <condition_variable>
template<typename T>
class SyncQueue
{
bool IsFull()const
{
return mqueue.size()==m_maxSize;
}
bool IsEmpty()const
{
return m_queue.empty();
}
public:
SyncQueue(int maxSize):m_maxSize(maxSize)
{
}
void Put(const T& x)
{
std::lockguard<std::mutex> locker(m_mutex);
while(IsFull)
{
cout<<"缓冲区已满,请等待..."<<endl;
m_notFull.wait(m_mutex);
}
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Take(T& x)
{
std::lock_guard<std::mutex> locker(mutex);
while(IsEmpty())
{
cout<<"缓冲区空了,需要等待..."<<endl;
m_notEmpty.wait(m_mutex);
}
x=m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size()==m_maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
int count()
{
return m_queue.size();
}
private:
std::list<T> m_queue;
std::mutex m_mutex;
std::condition_variable_any m_notEmpty;
std::condition_variable_any m_notFull;
int m_maxSize;
}
注意:wait函数中会释放mutex,然后处于等待状态,在被notify_one或notify_all唤醒后会先获取mutex,相当于lock_guard的mutex在释放之后又获取到.
原子变量:原子类型std::atomic<T>,可以使用任意类型为模板参数,使用原子变量就不需要使用互斥变量来保护该变量,下述为使用原子变量实现的计数器:
#include <atomic>
struct AtomicCounter{
std::atomic<int> value;
void increment()
{
++value;
}
void decrement()
{
--value;
}
int get()
{
return value.load();
}
}
call_once/once_flag的使用:
能保证在多线程环境中某个函数仅被调用一次,可以用std::call_once来保证函数仅被调用一次,使用的时候需要一个once_flag作为call_once的入参。
std::once_flag flag;
void do_once()
{
std::call_once(flag,[](){std::cout<<"called oncce"<<std::endl;});
}
异步操作:
主要有:std::future,std::promise,std::package_task
std::future作为异步结果的传输通道,可以很方便地获取线程函数的返回值;std::promise用来包装一个值,将数据和future绑定起来,方便线程赋值;std::package_task用来包装一个可以调用的对象,将函数和future绑定起来,以便异步调用。
1、future用来访问异步操作的结果,可以通过查看future的状态(future_status)来获取操作的结果,future有一下三种状态:
a)、Deferred:异步操作还没有开始
b)、Ready:异步操作已经完成
c)、Timeout:异步操作超时
eg:std::future_status status;
status=future.wait_for(std::chrono::seconds(1));
获取future结果有3种方式:get、wait、wait_for,get等待异步操作结束并返回结果;wait只是等待异步操作完成,没有返回值;wait_for是超时等待返回结果。
2、协助线程赋值的类std::promise
std::promise将数据和