[C++] mutex 互斥量

mutex 互斥量

多线程访问共享资源时,为了保证资源的线程安全,有必要做互斥处理。C++11 的mutex相比于pthread库提供互斥量使用起来更方便灵活。

C++11提供如下4种语义的mutex:

  • std::mutex,独占的互斥量。
  • std::time_mutex,带超时的独占互斥量。
  • std::recursive_mutex,递归互斥量。
  • std::recursive_timed_mutex,带超时的递归互斥量。

独占的互斥量 mutex

常用成员函数

  • lock() ——互斥量加锁。线程调用该函数后存在 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁;(2). 如果当前互斥量被其他线程锁住,则当前线程会阻塞;(3). 如果当前线程重复加锁,则会产生死锁(deadlock)。
  • unlock() ——互斥量解锁。
  • try_lock() ——尝试加锁,如果互斥量已被其他线程加锁则当前调用立即返回,不阻塞。

示例代码

下面代码中,一般只有当线程函数使用increaseCounterByLock()版本时,counter的最终值才是正确的;使用increaseCounterByTryLock()版本时,最终的计数值会比前者要小,显然这是因为try_lock()并非每一次都能成功。

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
volatile int counter = 0;
mutex mtx;

void increaseCounterByNoLock()
{
    for(int i = 0; i < 10000; i++)
        counter++;
}

void increaseCounterByLock()
{
    for(int i = 0; i < 10000; i++)
    {
        mtx.lock();
        counter++;
        mtx.unlock();
    }
}

void increaseCounterByTryLock()
{
    for(int i = 0; i < 10000; i++)
    {
        if(mtx.try_lock())
        {
            counter++;
            mtx.unlock();
        }
    }
}

int main()
{
    thread threads[10];

    for(auto& t : threads)
        t = thread(increaseCounterByNoLock);
    for(auto& t : threads)
        t.join();
    cout << "counter through increaseCounterByNoLock is " << counter << endl;

    counter = 0;
    for(auto& t : threads)
        t = thread(increaseCounterByLock);
    for(auto& t : threads)
        t.join();
    cout << "counter through increaseCounterByLock is " << counter << endl;

    counter = 0;
    for(auto& t : threads)
        t = thread(increaseCounterByTryLock);
    for(auto& t : threads)
        t.join();
    cout << "counter through increaseCounterByTryLock is " << counter << endl;
}

输出打印:

counter through increaseCounterByNoLock is 78146
counter through increaseCounterByLock is 100000		# correct
counter through increaseCounterByTryLock is 51825

递归互斥量 recursive_mutex

递归互斥量允许一个线程多次地加锁,它的用法跟 mutex 几乎一样,但可以用来解决同一线程多次加锁导致的死锁问题。

尽管如此,我们还是不建议使用递归互斥量,而应该改进程序的逻辑。

带超时的互斥量 time_mutex

timed_mutex 只是比 mutex 多了两个可以设置超时时间的方法:try_lock_fortry_lock_until

try_lock_for设置一个超时时间,超时后还没获得锁就立即返回;

try_lock_until设置一个时间点,到达时间点后还没获得锁就立即返回,用法参考std::timed_mutex::try_lock_until

代码示例:

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
using ms = chrono::milliseconds;
using this_thread::sleep_for;
using this_thread::get_id;
timed_mutex mtx;

void work()
{
    ms timeout(100);
    while(true)
    {
        if(mtx.try_lock_for(timeout))
        {
            cout << "Thread " << get_id() << " work with lock...\n";
            ms duration(300);
            sleep_for(duration);
            mtx.unlock();
            sleep_for(duration);
        }
        else
        {
            cout << "Thread " << get_id() << " work without lock...\n";
            ms duration(500);
            sleep_for(duration);
        }
    }
}

int main()
{
    thread t1(work);
    thread t2(work);

    t1.join();
    t2.join();
}

lock_guard和unique_lock

这二者是互斥量的 RAII(通过类的构造析构)实现,类似于智能指针。

使用 lock_guard 时,可以这样给某一端临界区域的代码加锁:

// ......
{
	lock_guard<std::mutex> locker(mtx);
	.....
} // 出了大括号的区域后,lock_guard 被析构,自动解锁
// ......

在一块代码段中。将mutex丢给 unique_lock 与 lock_guard 类的实例都能实现自动加锁和解锁,但是unique_lock更加灵活,因为unique_lock允许主动调用unlock,但lock_guard不提供这个接口,只有在其析构时才能解锁。所以,在有需要主动调用unlock的场景必须使用unique_lock。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值