C++11——多线程开发

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;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值