深入应用C++11 笔记---互斥量 (七)

深入应用C++11 笔记—互斥量 (七)

互斥量

互斥量是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问共享数据。C++11中提供如下4种语义的互斥量(mutex):

  • std::mutex:独占的互斥量,不能递归使用
  • std::timed_ mutex:带超时的独占互斥量,不能递归使用
  • std::recursive_mutex:递归互斥量,不带超时功能
  • std::recursive_timed_mutex:带超时的递归互斥量

1.1 独占互斥量std::mutex

​ 一般是通过lock()方法来阻塞线程直到获得互斥量的所有权为止,当线程获得互斥量并完成任务之后,必须使用unlock()解除对互斥量的占用try_lock()尝试锁定互斥量,如果成功则返回true,否则返回false,它是非阻塞的。

//std::mutex基本用法
#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>

std::mutex g_lock;
void func()
{
    g_lock.lock();//lock
    std::cout<<"entered thread  "<<std::this_thread::get_id()<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout<<"leaving thread  "<<std::this_thread::get_id()<<std::endl;
    g_lock.unlock();//unlock
}

int main()
{
    std::thread t1(func);
    std::thread t2(func);
    std::thread t3(func);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

结果输出(使用CLion 2018.1.5版本):

entered thread  2
leaving thread  2
entered thread  3
leaving thread  3
entered thread  4
leaving thread  4

lock_guard可以简化lock/unlock,因为lock_guard在构造时会自动锁定互斥量,在离开作用域后进行析构就会自动解锁

lock_guard用到了RAII技术,这种技术在类的构造函数中分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放。

void func()
{
    std::lock_guard::mutex> locker(g_lock);//出作用域后自动解锁
    std::cout<<"entered thread  "<::this_thread::get_id()<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout<<"leaving thread  "<::this_thread::get_id()<<std::endl;
}

1.2 递归互斥量 std::recursive_mutex

​ 递归锁允许同一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。如下所示,一个线程多次获取同一个互斥量时会发生死锁:

struct Complex
{
    std::mutex mutex;
    int i;
    Complex():i(0){}
    void mul(int x)
    {
        std::lock_guard<std::mutex> lock(mutex);
        i*=x;
    }
    void div(int x)
    {
        std::lock_guard<std::mutex> lock(mutex);
        i/=x;
    }
    void both(int x,int y)
    {
        std::lock_guard<std::mutex> lock(mutex);//此处获取互斥量
        mul(x);//再次获取互斥量就会发生死锁
        div(y);
    }
};

int main()
{
    Complex complex;
    complex.both(32,23);
    return 0;
}

上面例子运行后就会发生死锁,因为在调用both时获取了互斥量,之后在调用mul又要获取相同的互斥量,但是这个互斥量已经被当前线程获取了,无法释放,就会发生死锁。解决这个死锁问题,一个简单的方法就是使用递归锁:std::recursive_mutex,它允许同一线程多次获得互斥量,代码如下:

struct Complex
{
    std::recursive_mutex mutex;
    int i;
    Complex():i(0){}
    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 complex;
    complex.both(32,23);//因为同一线程可以多次获取同一互斥量,不会死锁
    return 0;
}

注意:尽量不要使用递归锁,原因如下:

  1. 需要用到递归锁定的多线程互斥处理往往本身可以简化,允许递归互斥很容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题。
  2. 递归锁比起非递归锁,效率会低一些。
  3. 递归锁虽然允许同一个线程多次获得同一个互斥量,可重复获得最大次数并未具体说明,一旦超过一定次数,再对lock进行调用就会抛出std::system错误

1.3 带超时的互斥量 std::time_mutex和std::recursive_timed_mutex

​ std::time_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用在获取锁时增加超时等待等功能,因为有时候不知道获取锁需要等多久,为了不至于一直在等待获取互斥量,就设置一个等待超时时间,在超时后还可以做其他事情。

​ std::time_mutex比std::mutex多了两个超时获取锁的接口:try_lock_fortry_lock_until,这两个接口用来设置获取互斥量的超时时间,使用时可以用一个while循环去不断地获取互斥量。std::time_mutex的基本用法如下:

std::time_mutex mutex;
void work()
{
    std::chrono::milliseconds timeout(100);
    while(true)
    {
        if(mutex.try_lock_for(timeout))
        {
            std::cout<::this_thread::get_id()<<": do work with the mutex"<<std::endl;
            std::chrono::milliseconds sleepDuration(250);
            std::this_thread::sleep_for(sleepDuration);
            mutex.unlock();
            std::this_thread::sleep_for(sleepDuration);
        }
        else
        {
            std::cout<::this_thread::get_id()<<": do work without mutex"<<std::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;
}

std::recursive_timed_mutex可以看做在std::recursive_timed_mutex的基础上加了超时功能。

声明:以上主要来源于深入应用C++11 这本书,强烈推荐

你可能感兴趣的:(C++,1z)