C++多线程中的互斥量、锁

本文详细介绍了C++中互斥量(mutex)的概念和作用,作为线程同步的一种手段,互斥量用于保护多线程对公共变量的访问。文中提到了不同类型的mutex,如std::mutex、std::recursive_mutex等,并指出lock_guard的使用可以避免死锁,确保资源在作用域结束时得到释放。通过示例展示了lock_guard和mutex的使用,强调了其在异常处理中的重要性。
摘要由CSDN通过智能技术生成

一、简介

多线程同时访问公共变量时,必须对变量进行保护,避免同时的“写-写”和“读-写”访问,

但变量本身是无法被保护的,毕竟变量只是一段可读写的内存区域而已。

于是,我们定义了一种新的变量 —— 互斥量//mutex,这是一种可以被保护的量。

互斥量是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问的共享数据.

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

这些互斥量的基本接口十分相近,都是通过lock()来阻塞线程,直到获得互斥量的所有权为止。在线程或的互斥量并完成任务后,就必须使用unlock()来解除对互斥量的占用,lock和unlock必须成对出现。try_lock()尝试锁定互斥量,成功返回true,失败返回false,他是非阻塞的。

所谓保护,即:同一时间只能被一个函数(或一段代码)访问,访问的方式是“mutex.lock()”;访问过后必须要释放以便别人去访问,释放的方式是“mutex.unlock()”。

我们约定:所有的函数访问公共变量时,必须先拿起互斥量——执行mutex.lock(),才有资格进行访问;访问结束后,必须放下互斥量——执行mutex.unlock()。

理解:

  1. 之所以会出现所谓的“互斥量(mutex)”,完全是因为我们进行多线程编成而导致的(两个线程都会对某一个全局变量操作比如:我想让全局变量a = a+1, A线程确实写的程序是a = a+1,但在B线程中,a = 100, 这就矛盾了);如果系统是单线程,程序只需要按照顺序执行就可以了,根据不需要互斥量!
  2.  针对这个问题,当我们把 a = a+1 放在 mutex.lock()”和“mutex.unlock()”之间时,只会执行a = a+1,而不会执行 线程B中的a = 100;
  3. 再次提醒,互斥量(mutex)是针对全局变量,全局变量...........,记住啦

你可以想象一个将军想要从皇帝手里调兵(公共变量),那他必须拿到兵符,兵符只有一个,将军A用的时候,将军B就不能用了;将军A用完,必须还给皇帝,以便其他将军使用。兵符,就是C++中的互斥量。

这样,我们间接实现了对公共变量的访问保护。

从效果上看,互斥量起到了“唯一兵符”的作用,基于“唯一兵符”的访问保护是建立在所有访问者都遵守“约定”的基础上。也即,我们并未对公共变量本身有任何保护,我们通过遵守“约定”达成公共秩序,实现对公共变量的保护。

如果某个访问者(函数或代码段)不遵循公约,就会造成公共数据访问的混乱,引起数据错误的灾难。

以上,是关于互斥量设计的最基本的思想。实际开发中,显式地让程序员去执行mutex.lock()、mutex.unlock()是不被推崇的,如果程序员不小心没有释放mutex,会造成死锁。

为此,C++定义了 lock_guard类型,用来管理 mutex。

lock_guard 类型对象构造时以 mutex 为参数,析构时释放mutex。

怎么用 lock_guard 呢?我们在函数(或代码段)开始时定义一个 lock_guard 类型的变量,这个变量是局部的,作用域为函数体内部(或代码段内部),当函数执行结束时,该变量会自动析构,从而释放 mutex 。

但 lock_guard 实际上还有更重要的意义:当某一个使用了 lock_guard 的线程异常结束时,lock_guard 对象同样会自动析构,释放 mutex,避免别的线程永久等待(也即死锁)。

为满足各种各样的实际需求,mutex延伸出了share_mutex 等,lock_guard 则延伸出了unique_lock(手动加锁)、shared_lock、scoped_lock 等,这些延伸出的类型各有特点,开发者根据需要选用即可。

 lock_guard和unique_lock的区别

  • 区域锁lock_guard使用起来比较简单,除了构造函数外没有其他member function,在整个区域都有效。
  • 区域锁unique_guard除了lock_guard的功能外,提供了更多的member_function,相对来说更灵活一些。

unique_guard的最有用的一组函数为:

lock

locks the associated mutex 
(public member function)

try_lock

tries to lock the associated mutex, returns if the mutex is not available 
(public member function)

try_lock_for

attempts to lock the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration 
(public member function)

try_lock_until

tries to lock the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached 
(public member function)

unlock

unlocks the associated mutex 

二、示例

示例包括:
1、使用lock加锁和unlock解锁
2、使用lock_guard:用到了RAII的技术,这种技术在类的构造函数中分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放。

#include "stdafx.h"

static std::mutex g_lock;

void lock_unlock()
{
    //上锁
    g_lock.lock();
    cout << "in id: " << this_thread::get_id() << endl;
    this_thread::sleep_for(chrono::seconds(1));
    cout << "out id: " << this_thread::get_id() << endl;
    //解锁
    g_lock.unlock();
}

void f_lock_guard()
{
    //lock_guard在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁.
    lock_guard<std::mutex> lock(g_lock);
    cout << "in id: " << this_thread::get_id() << endl;
    this_thread::sleep_for(chrono::seconds(1));
    cout << "out id: " << this_thread::get_id() << endl;
}

int mutex_demo()
{
    std::thread t1(lock_unlock);
    std::thread t2(lock_unlock);
    std::thread t3(lock_unlock);

    t1.join();
    t2.join();
    t3.join();

    std::thread t4(f_lock_guard);
    std::thread t5(f_lock_guard);
    std::thread t6(f_lock_guard);

    t4.join();
    t5.join();
    t6.join();

    return 0;
}

参考:

c++多线程(二)互斥量_天涯遍地是小草的博客-CSDN博客

C++笔记之多线程的理解与应用_sinat_22336563的博客-CSDN博客

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

他人是一面镜子,保持谦虚的态度

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值