一点一滴成长

导航

boost--线程同步

 1、互斥锁(互斥量)

  mutex是独占式的互斥锁。timed_mutex增加了超时功能。

  成员函数:lock()用于锁定,try_lock()为非阻塞版本的锁定,unlock()用于解锁。timed_lock()只属于timed_mutex,它可以等待一定的时间,等待的时间可以是一个时间段,也可以是指定的时间。

  使用方法:使用mutex必须配合try-catch块以保证解锁互斥量,eg:

#include "boost\thread.hpp"

int main()
{
    boost::mutex mu;
    try
    {
        mu.lock();
        cout << "Need to be protected" << endl; //io流是个共享资源,多线程内使用的话需要同步
        mu.unlock();
    }
    catch (...)
    {
        mu.unlock();
    }

    return 0;
}
View Code

  mutex还提供了一系列的RAII型的互斥锁,用于取消麻烦的try-catch块,它会在构造的时候锁定互斥量,析构时自动解锁,eg:

#include "boost\thread.hpp"

int main()
{
    boost::mutex mu;
    boost::mutex::scoped_lock lock(mu);
    cout << "Need to be protected" << endl; //io流是个共享资源,多线程内使用的话需要同步

    return 0;
}
View Code

  使用实例:以下定义了一个支持原子前++的计数器atom_increase:

#include "boost\thread.hpp"

template<typename T>
class atom_increase
{
public:
    atom_increase(T x = 0): n(x){} //转换构造函数,实现T类型到atom_increase类型的隐式转换

    T operator++() //重载前++运算符
    {
        boost::mutex::scoped_lock lock(mu);
        return ++n;
    }

    operator T() { return n; } //类型转换函数,实现atom_increase类型到T类型的隐式转换

private:
    T n;
    boost::mutex mu;
};

int main()
{    
    atom_increase<int> n = 0; //隐式转换:int -> atom_increase
    ++n; //原子前++
    int i = 5 + n; //隐式转换:atom_increase ->int

    return 0;
}
View Code

2、递归锁

  recursive_mutex是递归锁,可以多次锁定,相应的也要多次解锁。recursive_timed_mutex增加了超时功能。递归锁的使用接口跟互斥锁基本相同。

3、读写锁

  shared_mutex是读写锁,提供了multiple-reader / single-writer功能。读取锁定时我们使用shared_lock<shared_mutex>对象,写入锁定时我们使用unique_lock<shared_mutex>对象:

    boost::shared_mutex rw_mu;

    //read thread
    {
        boost::shared_lock<boost::shared_mutex> sl(rw_mu); //读锁定
        //......
    }
    
    //write thread
    {
        boost::unique_lock<boost::shared_mutex> ul(rw_mu); //写锁定
        //......
    }
View Code

 4、条件变量

  condition_variable_any是条件变量,它用来在一个线程中等待某个事件的发生(满足某个条件),另一个线程会使条件成立。条件变量需要与一个互斥量配合使用。condition_variable_any::wait()用来等待条件满足,wait_for()用来等待条件满足直到超时,wait_until()用来等待条件满足直到指定的时间, condition_variable_any::notify_one() / notify_all()用来在条件满足的时候通知条件变量。

  boost中的条件变量的使用方法与posix或windows下条件变量使用方法基本一致:

 

#include "boost\thread.hpp"

boost::condition_variable_any g_cd;
boost::mutex g_mu;
bool g_bConditionFlag = false;

void Thread1Proc()
{
    boost::mutex::scoped_lock lock(g_mu);
    while (!g_bConditionFlag) //wait()方法返回也可能是线程的私自苏醒导致的,所以还需要判断条件
    {
        boost::this_thread::sleep(boost::posix_time::seconds(1));
        g_cd.wait(g_mu);
    }

    printf("thread1 exit\n");
}

int main()
{
    boost::thread t(Thread1Proc);
    t.detach();
    boost::this_thread::sleep(boost::posix_time::milliseconds(100));

    g_mu.lock();
    g_bConditionFlag = true;
    g_cd.notify_one();
    g_mu.unlock();

    printf("here\n");
    getchar();

    return 0;
}
View Code

 

 5、barrier

  barrier称为护栏,它可以用来设置线程执行到barrier时必须等待,直到所有线程都达到这个点时才继续执行。

 6、C++11中的线程操作

  c++11也提供了创建线程和线程同步的方法,c++11里的mutex,与boost里的mutex用法类似:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock

std::mutex foo, bar;

void task_a()
{
    // foo.lock(); 
    // bar.lock();
    std::lock(foo, bar);
    std::cout << "task a\n";

    foo.unlock();
    bar.unlock();
}

void task_b()
{
    // bar.lock(); 
    // foo.lock();
    std::lock(bar, foo);
    std::cout << "task b\n";
    bar.unlock();
    foo.unlock();
}

int main()
{
    std::thread th1(task_a);
    std::thread th2(task_b);
    // th1.detach();
    // th2.detach();
    th1.join();
    th2.join();

    return 0;
}
View Code

  创建线程的时候需要注意三点:

  ①、如果使用函数对象作为thread的参数的话,直接传入临时对象会出错,可以定义一个对象传入或者使用lambda表达式:

class CTask
{
public:
    void operator()()
    {
        int a = 0;
    }
};

//std::thread th1(CTask()); //直接传入临时对象会出错
CTask task;
std::thread th1(task);
th1.join();
View Code

  ②、传递给线程函数的参数是先保存在于一个中转站中,当函数执行的时候再传给函数的形参,而这个时候传递的参数指向的值很有可能已经失效,所以,对于线程函数传递的参数应该与形参类型相同,而不是再进行转换。

  ③、如果线程函数的参数是引用的话传入时还需要结合ref来使用。

    void task(int& a, string str)
    {

    }

    int iNum = 0;
    char* pStr = new char[100];
    strcpy(pStr, "test");
    //std::thread th(task, 5, std::ref(iNum), pStr); //不应该直接传入pStr
    std::thread th(task, std::ref(iNum), string(pStr)); //应该传入对应类型
    delete[] pStr;
    th.join();
View Code

  c++中的lock_guard、unique_lock也是RAII型的互斥锁,类似boost的scoped_lock,使用示例:

    std::lock_guard<std::mutex> _lock(m_mutex);
    g_num++;

  unique_lock在lock_guard的基础上增加了加锁和解锁的接口方法,对比下面的fun1和fun2,二者效果相同,通过代码可以看出二者区别:

std::mutex g_mu;

void fun1()
{
    {
        std::lock_guard<std::mutex> guard(g_mu);
        //do something 1
    }

    //do something 2
    
    {
        std::lock_guard<std::mutex> guard(g_mu);
        // do something 3
    }
}

void fun2()
{
    std::unique_lock<std::mutex> guard(g_mu);
    //do something 1
    guard.unlock();

    //do something 2

    guard.lock();
    // do something 3
}
View Code

  unique_lock可以配合条件变量来使用,而lock_guard不能,因为lock_guard没有加锁和解锁的接口方法。另外,unique_lock和lock_guard都不能复制,unique_lock可以移动,lock_guard不能移动。c++11中的条件变量condition_variable与boost的使用类似,其接口方法有wait()、notify_one()等。

  下面是C++中使用条件变量的示例:

#include <iostream>
#include <thread> 
#include <mutex>

std::mutex g_Mutex;
std::condition_variable g_ConditionLock;
bool g_bFlag = false;

void task()
{
    std::unique_lock <std::mutex> lk(g_Mutex);
    //使用带lambda参数的wai(),这样就不用循环检测g_bFlag
    g_ConditionLock.wait(lk, [] { return g_bFlag; });
    //上面语句相当于下面
    /*while (!g_bFlag) //循环检测防止假唤醒
    {
        g_ConditionLock.wait(lk);
    }*/

    printf("condition set\n");
}

int main()
{
    std::thread th1(task);
    th1.detach();
    std::this_thread::sleep_for(std::chrono::seconds(3));

    {
        //这次触发条件不会唤醒等待线程,因为g_bFlag为false
        std::unique_lock <std::mutex> lk(g_Mutex);
        g_ConditionLock.notify_one();
    }

    std::this_thread::sleep_for(std::chrono::seconds(3));

    {
        //这次触发条件会唤醒等待线程,因为g_bFlag为true
        std::unique_lock <std::mutex> lk(g_Mutex);
        g_bFlag = true;
        g_ConditionLock.notify_one();
    }

    getchar();

    std::cout << "main\n";
}
View Code

  需要注意的是,上面的task()方法中使用新版本的condition_variable::wait()方法来替代了循环检测,新旧版本的wai()功能会有不同:当我们将条件g_bFlag置为true并调用notify_one()来唤醒task()中的条件变量后,再次进行wait()的话——如果是新版本的wait()方法,只要g_bFlag为ture则wait()会立即返回,如果使用的是老版本的wait()方法的话,则wait()会一直等待。

   C++11提供了原子类atomic<>、atomic_int、atomic_bool等:

 

    std::atomic<int> d1 = 100; //原子赋值
    d1 += 10; //原子+=
    d1.store(100); //原子赋值
    int d = d1.load(); //原子获取值
    d1.fetch_add(10); //原子加10,并返回加之前的值

    int compareValue = 1001;
    int newValue = 101;
    bool b = d1.compare_exchange_strong(compareValue, newValue); //原子判断并赋值:如果与某值相等的话就设置为新值并返回true,否则返回false
View Code

 

   在《Java线程》这篇文章中说过使用CAS技术来实现自旋锁,C++中atomic的compare_exchange_strong()就是CAS技术的体现,另外还有TAS技术,TAS和CAS都是硬件同步的指令,只不过它俩的同步方式不太一样。atomic_flag的test_and_set()就是TAS技术的体现,test_and_set()会设置当前atomic_flag,并返回在这次设置之前是否已经被设置了(true/false,atomic_flag的clear()可以清除当前的设置)。使用TAS也可以实现自旋锁:

#include <atomic>

class spin_lock
{
public:
    void lock()
    {
        while(_flg.test_and_set()){}
    }
    bool try_lock()
    {
        int try_cnt = 100;
        for (; try_cnt > 0 && _flg.test_and_set(); --try_cnt);

        if (try_cnt > 0)
            return true;
        return false;
    }
    void unlock()
    {
        _flg.clear();
    }
private:
    spin_lock(const spin_lock&) = delete;
    spin_lock(spin_lock&&) = delete;
    spin_lock& operator=(const spin_lock&) = delete;
    spin_lock& operator=(spin_lock&&) = delete;

    std::atomic_flag _flg = ATOMIC_FLAG_INIT;
};
View Code

 

posted on 2017-09-29 09:28  整鬼专家  阅读(2802)  评论(0编辑  收藏  举报