线程的创建
C++11用std::thread创建线程,我们只需提供线程函数或者函数对象即可,并且可以同时指定线程函数的参数。
#include <thread>
using namespace std;
void func(){
//
}
int main(){
thread t(func);
t.join();
return 0;
}
上面join()是不是很熟悉啊,系滴,java里之前写多线程的时候也讨论过,join函数将会阻塞线程,知道线程函数执行结束,如果线程函数有返回值,返回值会被忽略。
如果不想线程被阻塞,可以用线程的detach()方法,将线程和线程对象分离。
#include <thread>
using namespace std;
void func(){
//
}
int main(){
thread t(func);
t.detach();
return 0;
}
这样线程和线程对象就分离了,让线程作为后台线程执行。但是detach之后就无法在和线程发生联系了,线程何时执行完,我们也无法控制了。
Note:
std::thread 出了作用域之后将会被析构,这时如果线程函数还没有执行完则会发生错误,因此需要保证线程函数的生命周期在线程变量的生命周期之内。
线程不能复制但是可以移动。线程移动之后,原线程将不再代表任何线程。
一定要小心处理线程对象的生命周期在线程函数执行完时仍然存在。除了上面的join,detach方法之外, 还可以将线程对象保存到一个容器中,以保证线程对象的生命周期:
#include<thread>
using namespace std;
vector<thread> ret1;
vector<shares_ptr<thread>> ret2;
void CreateThread{
thread t(func);
ret1.push_back(move(t));
ret2.push_back(make_shared<thread>(func));
}
int main(){
CreateThread();
for(auto& thread: ret1){
thread.join();
}
for(auto& thread:ret2){
thread->join();
}
return 0;
}
我们还可以获取当前线程的ID,获取CPU的核心数量,还可以让线程休眠等基本操作。
互斥量
C++11提供了四种互斥量:
std::mutex:独占的互斥量,不能递归
std::timed_mutex:带超时的独占互斥量,不能递归使用
std::recursive::mutex:递归互斥量,不带超时功能
std::recursive_timed_mutex:带超时的递归互斥量
其实学过操作系统的同学应该一目了然吧,确实是跟操作系统的互斥量操作很像。举个简单的例子:
#include <mutex>
#include <thread>
#include <iostream>
#include <chrono>
using namespace std;
mutex locker;
void func(){
locker.lock();
//do sth
locker.unlock();
}
殊不知:我们还有一个lock_guard可以简化lock/unlock。而且更安全。因为lock_guard在构造时会自动锁定互斥量,而且推出作用域后进行析构时就会释放锁。保证了互斥量的正确操作,避免忘记Unlock操作。用到了所谓RAII技术,在类的构造函数中分配资源,在析构函数中释放资源,保证资源在除了作用域之后就释放。
void func(){
lock_guard<mutex> lockers(locker);
//do sth
}
是不是更简化而且逻辑明了啊。
而对于递归互斥量,可以用来解决统一鲜橙需要多次获取互斥量时的死锁问题。
struct multiOpera{
mutex m_mutex;
int i;
multiOpera():i(0){};
void add(int x){
lock_guard<mutex> lockers(m_mutex);
i += x;
}
void sub(int x){
lock_guard<mutex> lockers(m_mutex);
i -= x;
}
void multi(int x, int y){
lock_guard<mutex> lockers(m_mutex);
add(x);
sub(y);
}
}
直接死锁
但是用递归互斥量可以解决这个问题
struct multiOpera{
recursive_mutex m_mutex;
int i;
multiOpera():i(0){};
void add(int x){
lock_guard< recursive_mutex> lockers(m_mutex);
i += x;
}
void sub(int x){
lock_guard< recursive_mutex> lockers(m_mutex);
i -= x;
}
void multi(int x, int y){
lock_guard< recursive_mutex> lockers(m_mutex);
add(x);
sub(y);
}
}
但是我们还是要尽量少用这种递归锁:
需要用到的递归锁丁的多线程互斥处理本身其实是可以简化的,允许递归互斥很容易放纵复杂逻辑的产生,导致一些多线程同步引起的问题。
递归锁效率更低一些。
尽管递归锁允许同一个线程多次获取同一个互斥量,但是获得的超过最大次数,再次lock调用就会抛出system错误。
而超时独占锁跟超时的递归锁就是增加了超时的功能,为了解决一直等待而遭到阻塞的问题。
std::timed_mutex m_mutex;
void dosth{
while(true){
if(m_mutex.try_lock_for(100)){
//获取锁
// dosth
}else{
//没获取到锁
//do other
}
}
}
条件变量
这个操作起来更像是操作系统的pv操作的生产者和消费者的问题。
用于线程之间的消息同步。
condition_variable配合std::unique_lockwait操作。
condition_variable_any,和任意带lock,unlock,的mutex搭配使用,更加灵活但是效率较低。
#include "stdafx.h"
#include <mutex>
#include <thread>
#include <condition_variable>
template<typename T>
using namespace std;
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){
lock_guard<mutex> locker(m_mutex);
m_notFull.wait(locker, [this]{return !IsFull();});
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Task(T& x){
lock_guard<mutex> locker(m_mutex);
while (IsEmpty())
{
m_notEmpty.wait(m_mutex);
}
x = m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
bool Empty(){
lock_guard<mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full(){
lock_guard<mutex> locker(m_mutex);
return m_queue.size == m_maxSize;
}
size_t Size(){
lock_guard<mutex> locker(m_mutex);
return m_queue.size();
}
int Count(){
return m_queue.size();
}
private:
list<T> m_queue; //
mutex m_mutex; //
condition_variable_any m_notEmpty; //
condition_variable_any m_notFull; //
int m_maxSize; //
};
这不就是一个典型的消费者和生产者的问题嘛,所以说啊,很多东西原理都是相同的,只不过具体做法不一样而已。
细心的读者应该发现了一个问题,Put和Task里面对wait的操作不一样,其实wait有一个重载的问题,可以添加一个条件变量。两种形式的是完成同样的操作。
下面再来看condition_variable的例子(简单的线程池)
#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>
using namespace std;
template<typename T>
class SimpleSyncQueue{
public:
SimpleSyncQueue(){}
void Put(const &x){
lock_guard<mutex> locker(m_mutex);
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Task(T& x){
unique_lock<mutex> locker(m_mutex);
m_notEmpty.wait(locker,[this]{return !m_queue.empty();});
x = m_queue.front();
m_queue.pop_front();
}
bool Empty(){
lock_guard<mutex> locker(m_mutex);
return m_queue.empty();
}
size_t Size(){
lock_guard<mutex> locker(m_mutex);
return m_queue.size();
}
private:
list<T> m_queue;
mutex m_mutex;
CONDITION_VARIABLE m_notEmpty;
};
原子变量
下面看一个对比
使用原子变量前:
struct Counter{
mutex m_mutex;
int value;
void increment{
lock_guard<mutex> locker(m_mutex);
++value;
}
void decrement{
lock_guard<mutex> locker(m_mutex);
--value;
}
int getValue{
return value;
}
};
使用原子变量后:
struct Counter{
atomic<int> value;
void increment{
++value;
}
void decrement{
--value;
}
int getValue{
return value;
}
};
是不是使用原子变量更加简化了我们的工作。
异步操作
只要涉及到三个类:
获取线程函数返回值的类:std::future
协助线程赋值的类:std::promise
可调用对象的包装类:std::packge_task
本人也是第一了解这个,很多东西也是云里雾里的,希望大家多多提意见,互相学习。
我们从一个例子看吧
future_status status;
do{
status = future.wait_for(chrono::seconds(1));
if(status == furture_status::deferred){
}else if (status == furture_status::timeout)
{
}
else if (status == furture_status::ready)
{
}
}while(status != future_status::ready);
#include <iostream>
#include <thread>
#include <utility>
#include <future>
using namespace std;
int func(int x) {return x+2;}
int main(){
pack_task<int(int)> tsk(func);
future<int> fut = tsk.get_future();//获取future
thread(move(tsk), 2).detach(); //task作为线程函数
int value = fut.get(); //等待task完成并获取结果
cout<< value <<endl;
//future不能复制,不能放到容器中,要用shared_future
vector<shared_future<int>> v;
auto f = async(launch::async, [](int a, int b){return a+b;},2,3);
v.push_back(f);
cout<<v[0].get()<<'\n';
return 0;
}
result:
4
5
是的,future用来访问异步操作的结果,提供了获取异步操作结果的通道。有三种future_status,从上面的代码也可以看出来:
Deferred:异步操作还没开始
Ready:异步操作已经完成
Timeout:异步操作超时
获取结果有3种操作:
get:等待异步操作结束返回结果
wait:等待异步操作结束
wait_for:超时等待返回结果
std::promise是将数据和future绑在一起,为了获取函数中某个值提供便利,取值是间接地通过promise内部提供的future来获取。
std::package_task包装了一个可调用对象的包装类。将函数和future绑定在一起。
* 异步操作之async*
async比promise,packaged_task,thread更高一层。可以用来直接创建异步task,一步任务的返回结果页保存在future中
#include <iostream>
#include <thread>
#include <utility>
#include <future>
using namespace std;
int main(){
future<int> m_future = async(launch::async,[](){this_thread::slep_for(chrono::seconds(3));return 8;});
cout << "waiting \n";
future_status status;
do
{
status = future.wait_for(chrono::seconds(1));
if(status == furture_status::deferred){
cout << "deferred \n";
}else if (status == furture_status::timeout)
{
cout << "timeout \n";
}
else if (status == furture_status::ready)
{
cout << "ready \n";
}
} while (status != future_status::ready);
cout << "result is" << future.get() <<'\n';
}
Result:
Waiting
timeout
timeout
ready
result is8
async有两种创建线程的策略:
std::launch::async:在调用async时就开始创建线程。
std::launch::deferred:延迟加载方式创建线程。直到调用了future的get或者wait时才创建线程。
我也是参考《C++11深入应用》,欢迎大家指出错误。