linux线程安全篇之----互斥

目录

 1.多线程造成的安全隐患:

2.互斥

2.1互斥:

2.1.1互斥概念

2.1.2互斥锁:

2.1.3互斥锁的计数器当中如何保证原子性

3.互斥锁的接口

3.1.初始化互斥锁的接口

3.1.1动态初始化:

3.1.2静态初始化(直接用宏来初始化互斥锁):

3.2.加锁接口

3.3.解锁接口

3.4.销毁接口


 

 

 

 1.多线程造成的安全隐患:

6ff7187e0c3b55b15d38bd9d9e2d7376.png

2.互斥

  • 2.1互斥:

  • 2.1.1互斥概念

  • 互斥的要做的事情:控制线程的访问时序。 当多 个线程能够同时访问到临界资源的时候,有 可能会导致线程执行的结果产生二义性。而互斥就是要保证多个线程在访问同一个临界资源,执行临界区代码的时候 (非原子性性操作(线程可以被打断) ),控制访问时序。让一个线程独占临界资源执行完,再让另外一个独占执行;
  • 2.1.2互斥锁:

  • 互斥锁的原理

    940b5883b6663d9aee884a5bf5a6af8f.png

  • 互斥 锁的本质就是0/1计数器,计 数器的取值只能为0或者1
    • 计数器的值为1 :表示当 前线程可以获取到互斥锁,从而去 访问临界资源
    • 计数器的值为0 :表示当前线程不可以获取到互斥锁,从而不能访问临界资源
  • 需要理解的是:并不是说线程不获取互斥锁不能访问临界资源,而是程序猿需要在代码当中用同一个互斥锁,去约束多个线程(这句话的意思就是说加锁时必须加的是同一把锁,当线程A拿到这把锁将这把锁改为0其他线程就无法访问了,但是当线程A被切换出去之后,其他进程加锁加的也是这一吧被A置为0的锁,只有当A访问结束将这把锁置为1其他线程才可以访问临界区资源)。否则线程A加锁访问,线程B访问临界资源之前不加锁,那也约束不了线程B。(也就说要在每一个线程代码中要访问一个临界区资源之前要先获取锁也就是加锁)
  • 2.1.3互斥锁的计数器当中如何保证原子性

  • 为什么计数器(锁)当中的值从0变成1,或者从1变成0 是原子性的呢?
  • 直接使用寄存器当中的值和计数器内存的值交换,而交换是一 条汇 编指令就可以完成的(如果要是用++或者--是不可以保证原子性的不能一步完成的)
  • 1.加锁的时候:寄存器当中的值设置为(0)
  • 第一种情况:计数器的值为1,说明锁空闲, 没有被线程加锁
  • 交换情况:加锁成功

    e4a3e18e2522070796c96fd751b3b819.png

  • 第二种情况:计数器的值为0,说明锁忙碌,被其他线程加锁拿走(此时当前线程进入等待状态:等待其他线程访问完毕将锁打开)
  • 交换情况:加锁失败

    d1535700e4fe53c962d9b5e1eaa52107.png

  • 2.解锁的时候:寄存器当中的值设置为(1)
  • 计数器的值为0,需要解锁, 进行一步交换。
  • 交换情况:加锁成功

    e6e9174f5ffead096bc03ce9f9487dea.png

  • 交换情况:加锁失败

    84595a3adfe8f98bf5dc568a1e1d0e38.png

3.互斥锁的接口

  • 3.1.初始化互斥锁的接口

  • 3.1.1动态初始化:

    fb15559856a72b548a4c62f4c729aba0.png

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

  • 参数:
  • pthread_mutex_t *互斥锁类型
  • mutex互斥锁指针:可以从堆上开辟一块空间传进去也可以定义一个对象
  • const pthread_mutexattr_t *restrict attr:互斥锁的属性(一般传递为NULL采用它的默认属性)

3.1.2静态初始化(直接用宏来初始化互斥锁):

  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;(是一个宏)
  • #include PTHREAD_MUTEX_INITIALIZER{{0,0,0,0,0,......}}这个宏是用来初始化结构体的(pthread_mutex_t这个类型就是一个结构体类型)这个宏用来初始化这个mutex结构体,我们定义一个lock初始化后进入gdb调试也可以查看这个锁的结构体,可以看到它里面有许多成员变量。

    20d7de1cb93b757449e64a78e30d830d.png

  • 3.2.加锁接口

    962b4378761942935a639c4f120fda80.png

  • 3.2.1int pthread_ mutex_ lock(pthread_ mutex_ t *mutex)(互斥锁的阻塞加锁接口,拿不到锁就阻塞等待,直到拿到锁) ;
  • 3.2.2int pthread. mutex_ trylock(pthread_ mutex_ t *mutex);(非阻塞加锁接口,拿锁的时候如果拿不到锁就直接返回了,也不等待,需要搭配循环使用,当其一直调用当其返回值为zero时循环结束,否则使用这个接口的后果就和我们上面说的,当一个进程对一块被占用的资源访问时,如果不拿到这个锁,也是可以访问的,这样就达不到互斥访问的目的了)

    079f9af692c79a35518c099c6ef4da2b.png

  • 3.2.3int pthread_ mutex_ _timedlock(pthread_ mutex_ t *restrict mutex,const struct timespec *restrict abs_ t imeout);

    5d1de5b69e75451313cba04952903e84.png

  • 3.3.解锁接口

  • int pthread_ mutex_ unlock(pthread_ mutex_ t *mutex);
  • 3.4.销毁接口

  • int pthread_mutex_destroy(pthread. _mutex_ _t *mutex);
  • 如果是动态初始化互斥锁,就需要销毁,如果是静态初始化就不用销毁。

    030f46721de7728db159d58b2c6e6352.png

  • 代码演示:
  • 我们让一个线程加锁,然后线程运行完它的代码就直接退出了,就不会将锁打开

    dcee20f70111a0e7dd4a4214485f87c2.png

  • 我们执行代码,代码直接运行完毕

    810e15837ffd3e81568e107f8c7087da.png

  • 我们查看进程的调用栈,可以看到这里只有一个线程,而且这个线程一直在等待这个锁。但是加锁的进程已经将锁锁上,而且退出,那么这把锁就永远被锁上,这种情况叫做死锁。

    1d277b90f31268c69f0c63d0ba958886.png

  • 我们要防止死锁这种情况发生就需要考虑在所有线程可能退出的地方来进行解锁,不要让进程退出之后把锁带走
  • 例如下面这种情况,在线程退出时将锁解开

    45cf3678f9228f0ff7284b3b2768efa4.png

  • 运行结果:并没有出现死锁的情况

    e6998c1928a359d995578bb68be65217.png

  • 我们在调试中也可以看到一开始由线程id为18979的线程打印全局变量

    e0526478115b0ac32fe8ee582f2656e0.png

  • 当g_val=50时由线程id为18978的线程接手

    2a0dd9d435cc3dc176d7e7150f8db90e.png

看到这里如果觉得有用不如点个赞再走吧!!!4dd43204aa2a4c548bcb6bd384dc6599.jpeg

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

月半木斤

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值