C++11之多线程

线程的创建
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深入应用》,欢迎大家指出错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值