C++11之前,语言本身没有对并发编程提供语言级别支持,在移植时存在诸多不便。
C++11增加了线程以及线程相关的类,支持并发编程。
线程的创建 std::thread
只需要提供线程函数或者函数对象即可,并且可以同时指定线程函数的参数
#include <iostream>
#include <thread>
void func()
{
//do some work
}
int main()
{
std::thread t(func);
t.join();
return 0;
}
join函数会阻塞线程,知道线程函数执行结束,。
如果不希望线程被阻塞执行,可以调用线程的detach方法,将线程和线程对象分离,让线程作为后台线程去执行。
注意:调用detach后,就失去了与线程的联系,无法调用join来等待线程执行完毕。
#include <iostream>
#include <thread>
void func()
{
//do some work
}
int main()
{
std::thread t(func);
t.detach(); //将线程与线程对象分离,让线程作为后台线程去执行
return 0;
}
线程还可以接收任意个数的参数
#include <iostream>
#include <thread>
using namespace std;
void func(int i,double d,const string& s)
{
cout<<i<<", "<<d<<", "<<s<<endl;
}
int main()
{
thread t1(func,1,2,"test");
t1.join();
return 0;
}
std::thread出了作用域之后将会析构,这时如果线程函数还没有执行完则发生错误,因此需要保证线程函数的生命周期在线程变量std::thread的生命周期内。
线程无法复制,但是可以移动。
线程函数也可以通过bind或lambda表达式创建线程。
#include <iostream>
#include <thread>
using namespace std;
void func(int a,int b)
{
//do some work
}
int main()
{
std::thread t(func,1,2);
//移动线程
std::thread t1(std::move(t));
//采用bind绑定线程函数
std::thread t2(std::bind(func,1,2));
//采用lambda表达式定义线程函数
std::thread t3([](int a,double b){cout<<a<<", "<<b<<endl;},1,2);
t.join();
t1.join();
t2.join();
t3.join();
return 0;
}
获取线程当前信息
get_id() 获取当前线程ID
std::thread::hardware_concurrency() 获取CPU的核心数量
#include <iostream>
#include <thread>
void func()
{
//do some work
}
int main()
{
std::thread t(func);
cout<<t.get_id()<<endl;
cout<<std::thread::hardware_concurrency()<<endl;
t.join();
return 0;
}
std::this_thread::sleep_for 线程休眠
#include <iostream>
#include <thread>
#include <chrono>
void func()
{
//do some work
std::this_thread::sleep_for(std::chrono::seconds(3));
cout<<"time out"<<endl;
}
int main()
{
std::thread t(func);
t.join();
return 0;
}
互斥量
一种同步的原语,一种线程同步的手段,用来保护多线程同时访问共享数据。
C++11中提供了4种语义的互斥量mutex
1、std::mutex 独占互斥量,不能递归使用
2、std::timed_mutex 带超时的独占互斥量,不能递归使用
3、std::recursive_mutex 递归互斥量,不带超时功能
4、std::recursive_timed_mutex 带超时的递归互斥量
互斥量的接口基本相似:
1、lock()方法阻塞线程;try_lock()尝试锁定互斥量,如果成功返回true,如果失败返回false。
2、unlock()方法解除线程
独占互斥量
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
static std::mutex g_lock;
void func()
{
g_lock.lock();
cout<<"entered thread "<<std::this_thread::get_id()<<endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
cout<<"leaving thread "<<std::this_thread::get_id()<<endl;
g_lock.unlock();
}
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/unlock写法,同时也更安全
lock_guard在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解除。保证了互斥量的正确操作,避免忘记unlock操作。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
static std::mutex g_lock;
void func()
{
std::lock_guard<std::mutex> locker(g_lock);
cout<<"entered thread "<<std::this_thread::get_id()<<endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
cout<<"leaving thread "<<std::this_thread::get_id()<<endl;
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
t1.join();
t2.join();
t3.join();
return 0;
}
递归互斥量 允许同一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁问题
但是尽量不要使用递归锁,原因:
1、需要用到递归锁的多线程互斥处理往往本身就是可以简化的,需要递归互斥很容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题
2、递归锁比起非递归锁,效率低一些
3、递归锁虽然允许同一个线程多次获得同一个互斥量,可重复获取的最大次数并未具体说明,一旦超过一定次数,再对lock操作,就会抛出系统错误std::system
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
struct complex
{
std::recursive_mutex mutex;
int i;
complex() : i(1) {}
void mul(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i*=x;
}
void div(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i/=x;
}
void both(int x,int y)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
complex cx;
cx.both(32,4);
cout<<cx.i<<endl;
return 0;
}
带超时的互斥量 try_lock_for try_lock_until 设置获取互斥量的超时时间
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
static std::timed_mutex g_mutex;
void work()
{
std::chrono::milliseconds timeout(100);
while(true)
{
if(g_mutex.try_lock_for(timeout))
{
cout<<std::this_thread::get_id()<<" : do work with the mutex"<<endl;
std::chrono::milliseconds sleepDuration(250);
std::this_thread::sleep_for(sleepDuration);
g_mutex.unlock();
std::this_thread::sleep_for(sleepDuration);
}
else
{
cout<<std::this_thread::get_id()<<" : do work without mutex"<<endl;
std::chrono::milliseconds sleepduration(100);
std::this_thread::sleep_for(sleepduration);
}
}
}
int main()
{
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
return 0;
}
条件变量
另外一种用于等待的同步机制,阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程,条件变量需要和互斥量配合使用
C++11提供了两种条件变量
condition_variable 配合std::unique_lock<std::mutex>进行wait操作
condition_variable_any 和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable差一些
条件变量的使用过程:
1、拥有条件变量的线程获取互斥量
2、循环检查某个条件,如果条件不满足,则阻塞知道条件满足,如果条件满足,则向下执行
3、某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有的等待线程。
condition_variable_any 条件变量的使用——同步队列
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <list>
using namespace std;
template <typename T>
class SyncQueue
{
bool IsFull() const
{
return m_queue.size()==m_maxSize;
}
bool IsEmpty() const
{
return m_queue.empty();
}
public:
SyncQueue(int maxSize):m_maxSize(maxSize)
{
}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_notFull.wait(m_mutex,[this]{return !IsFull();});
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Take(T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_notEmpty.wait(m_mutex,[this]{return !IsEmpty();});
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;
};
condition_variable 条件变量的使用——同步队列
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <list>
using namespace std;
template <typename T>
class SyncQueue
{
bool IsFull() const
{
return m_queue.size()==m_maxSize;
}
bool IsEmpty() const
{
return m_queue.empty();
}
public:
SyncQueue(int maxSize):m_maxSize(maxSize)
{
}
void Put(const T& x)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notFull.wait(locker,[this]{return !IsFull();});
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Take(T& x)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker,[this]{return !IsEmpty();});
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 m_notEmpty;
std::condition_variable m_notFull;
int m_maxSize;
};
原子变量
C++11提供了一个原子类型std::atomic<T>, 可以使用任意类型作为模板参数。
使用原子变量,就不需要使用互斥量来保护该变量了,用起来更简洁。
#include <iostream>
#include <atomic>
using namespace std;
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的入参。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
static std::once_flag g_flag;
void do_once()
{
std::call_once(g_flag,[](){cout<<"called once"<<endl;});
}
int main()
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
t1.join();
t2.join();
t3.join();
return 0;
}
异步操作
1、std::future 异步结果的传输通道,很方便的获取线程函数的返回值
2、std::promise 用来包装一个值,将数据和future绑定起来,方便线程赋值
3、std::package_task 用来包装一个可调用对象,将函数和future绑定起来,以便异步调用
4、std::async 用来直接创建异步的task,异步任务返回的结果也保存在future中,当需要获取异步任务的结果中。
std::future 访问异步操作的结果,可以通过查询future的状态(future_status)来获取异步操作的结果。std::future是不可拷贝的,只能移动,std::shared_future是可以拷贝的,当需要将future放在容器中则需要用shared_future。
future_status有如下三种状态:
1、Deferred 异步操作还没开始
2、Ready 异步操作已经完成
3、Timeout 异步操作超时
获取future结果的3种方式,get、wait、wait_for。
1、get等待异步操作结束并返回结果。
2、wait等待异步操作完成,没有返回值。
3、wait_for超时等待返回结果。
std::future_status status
do
{
status=future.wait_for(std::chrono::seconds(1));
if(status==std::future_status::deferred)
{
std::cout<<"deferred"<<std::endl;
}
else if(status==std::future_status::timeout)
{
std::cout<<"timeout"<<std::endl;
}
else if(status==std::future_status::ready)
{
std::cout<<"ready"<<std::endl;
}
}while(status!=std::future_status::ready)
std::promise 将数据和future绑定起来,为获取线程函数中的某个值提供便利。
std::promise<int> pr;
std::thread t([](std::promise<int>& p){p.set_value_at_thread_exit(9);},std::ref(pr));
std::future<int> f=pr.get_future();
auto r=f.get();
std::package_task 包装了一个可调用对象的包装类,将函数与future绑定起来,以便异步调用。
std::packaged_task<int()> task([](){return 7;});
std::thread t1(std::ref(task));
std::future<int> f1=task.get_future();
auto r1=f1.get();
std::package_task和shared_future的基本用法
#include <iostream>
#include <thread>
#include <utility>
#include <future>
#include <vector>
using namespace std;
//a simple task
int func(int x)
{
return x+2;
}
int main()
{
std::packaged_task<int(int)> tsk(func);
std::future<int> fut=tsk.get_future();
std::thread(std::move(tsk),2).detach();
int value=fut.get();
cout<<"The result is "<<value<<endl;
vector<std::shared_future<int>> v;
std::shared_future<int> f=std::async(std::launch::async,[](int a,int b){return a+b;},2,3);
v.push_back(f);
cout<<"The shared_future result is "<<v[0].get()<<endl;
return 0;
}
std::async 比std::package_task更高一层,可以用来直接创建异步的task,异步任务返回的结果也保存在future中。
当需要获取异步结果时,调用future.get()方法,
不关注异步结果,只是简单的等待任务完成,调用future.wait()方法。
std::async的原型是async(std::launch::async | std::launch::deferred, f, args...)
第一个参数是线程创建策略,有两种策略,默认的策略是立即创建线程。
std::launch::async 在调用async时就开始创建线程
std::launch::deferred 延迟加载方式创建线程,调用async时不创建线程,直到调用了future的get或者wait时才创建线程。
第二个参数是线程函数,第三个参数是线程函数的参数
#include <iostream>
#include <thread>
#include <utility>
#include <future>
using namespace std;
int main()
{
std::future<int> f1=std::async(std::launch::async,[](){return 8;});
cout<<f1.get()<<endl;
std::future<int> f2 = std::async(std::launch::async,[](){cout<<8<<endl;return 8;});
f2.wait();
std::future<int> future=std::async(std::launch::async,[](){std::this_thread::sleep_for(std::chrono::seconds(3));return 8;});
cout<<"waiting..."<<endl;
std::future_status status;
do
{
status=future.wait_for(std::chrono::seconds(1));
if(status==std::future_status::deferred)
{
cout<<"deferred"<<endl;
}
else if(status==std::future_status::timeout)
{
cout<<"timeout"<<endl;
}
else if(status==std::future_status::ready)
{
cout<<"ready"<<endl;
}
}while(status!=std::future_status::ready);
cout<<"result is "<<future.get()<<endl;
}