订阅
纠错
加入自媒体

一文搞懂多线程中各个难点

2021-06-28 14:13
一口Linux
关注

6.3.4互斥量的解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

对上述所有的加锁接口,都可使用该函数解锁

解锁的时候,会将互斥锁当中计数器的值从0变为1,表示其它线程可以获取互斥量

6.4互斥锁的本质

1、在互斥锁内部有一个计数器,其实就是互斥量,计数器的值只能为0或者为1

2、当线程获取互斥锁的时候,如果计数器当前值为0,表示当前线程不能获取到互斥锁,也就是没有获取到互斥锁,就不要去访问临界资源

3、当前线程获取互斥锁的时候,如果计数器当前值为1,表示当前线程可以获取到互斥锁,也就是意味着可以访问临界资源

6.5互斥锁中的计数器如何保证了原子性?

获取锁资源的时候(加锁):

1、寄存器当中值直接赋值为0

2、将寄存器当中的值和计数器当中的值进行交换

3、判断寄存器当中的值,得出加锁结果

两种情况:

例:4个线程,对同一个全局变量进行减减操作

#include

互斥锁是不公平的。

内核维护等待队列, 互斥量实现了大体上的公平;由于等待线程被唤醒后, 并不自动持有互斥量, 需要和刚进入临界区的线程竞争(抢锁), 所以互斥量并没有做到先来先服务。

6.7互斥锁的类型

1、PTHREAD_MUTEX_NORMAL:最普通的一种互斥锁。它不具备死锁检测功能, 如线程对自己锁定的互斥量再次加锁, 则会发生死锁。

2、
PTHREAD_MUTEX_RECURSIVE_NP:支持递归的一种互斥锁, 该互斥量的内部维护有互斥锁的所有者和一个锁计数器。当线程第一次取到互斥锁时, 会将锁计数器置1, 后续同一个线程再次执行加锁操作时, 会递增该锁计数器的值。解锁则递减该锁计数器的值, 直到降至0, 才会真正释放该互斥量, 此时其他线程才能获取到该互斥量。解锁时, 如果互斥量的所有者不是调用解锁的线程, 则会返回EPERM。

3、
PTHREAD_MUTEX_ERRORCHECK_NP:支持死锁检测的互斥锁。互斥量的内部会记录互斥锁的当前所有者的线程ID(调度域的线程ID) 。如果互斥量的持有线程再次调用加锁操作, 则会返回EDEADLK。解锁时, 如果发现调用解锁操作的线程并不是互斥锁的持有者, 则会返回EPERM。

4、自旋锁,自旋锁采用了和互斥量完全不同的策略, 自旋锁加锁失败, 并不会让出CPU, 而是不停地尝试加锁, 直到成功为止。这种机制在临界区非常小且对临界区的争夺并不激烈的场景下, 效果非常好。自旋锁的效果好, 但是副作用也大, 如果使用不当, 自旋锁的持有者迟迟无法释放锁, 那么, 自旋接近于死循环, 会消耗大量的CPU资源, 造成CPU使用率飙高。因此, 使用自旋锁时, 一定要确保临界区尽可能地小, 不要有系统调用, 不要调用sleep。使用strcpy/memcpy等函数也需要谨慎判断操作内存的大小, 以及是否会引起缺页中断。

5、PTHREAD_MUTEX_ADAPTIVE_NP:自适应锁,首先与自旋锁一样, 持续尝试获取, 但过了一定时间仍然不能申请到锁, 就放弃尝试, 让出CPU并等待。PTHREAD_MUTEX_ADAPTIVE_NP类型的互斥量, 采用的就是这种机制。

6.8死锁和活锁

线程1已经成功拿到了互斥量1, 正在申请互斥量2, 而同时在另一个CPU上,线程2已经拿到了互斥量2, 正在申请互斥量1。彼此占有对方正在申请的互斥量,结局就是谁也没办法拿到想要的互斥量, 于是死锁就发生了。

6.8.1死锁概念

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其它进程所占有不会释放的资源而处于一种永久等待的状态。

6.8.2死锁的四个必要条件

1、互斥条件:一个资源只能被一个执行流使用

2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源不会释放

3、不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺

4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

6.8.3避免死锁

1、破坏死锁的四个必要条件(实际上只能破坏条件2和4)

2、加锁顺序一致(按照先后顺序申请互斥锁)

3、避免未释放锁的情况

4、资源一次性分配

6.8.4活锁

避免死锁的另一种方式是尝试一下,如果取不到锁就返回。

int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);

这两个函数反映了一种,不行就算了的思想。

trylock不行就回退的思想有可能会引发活锁(live lock) 。生活中也经常遇到两个人迎面走来, 双方都想给对方让路, 但是让的方向却不协调, 反而互相堵住的情况 。活锁现象与这种场景有点类似。

线程1首先申请锁mutex_a后, 之后尝试申请mutex_b, 失败以后, 释放mutex_a进入下一轮循环, 同时线程2会因为尝试申请mutex_a失败,而释放mutex_b, 如果两个线程恰好一直保持这种节奏, 就可能在很长的时间内两者都一次次地擦肩而过。当然这毕竟不是死锁, 终究会有一个线程同时持有两把锁而结束这种情况。尽管如此, 活锁的确会降低性能。

6.8.5死锁调试

查看多个线程堆栈:thread apply all bt
跳转到线程中:t 线程号
查看具体的调用堆栈:f 堆栈号
直接从pid号用gdb调试:gdb attach pid
#include

在上述代码中,一定会出现死锁,线程1拿到了互斥锁1,又再去申请线程2的互斥锁2,线程2拿到了互斥锁2又再去申请线程1的互斥锁1。

开始调试:

1、找到进程号

2、开始调试

3、查看多个线程堆栈

4、跳转到线程中

5、查看具体调用堆栈

6、查看互斥锁1和互斥锁2,分别被谁拿着

<上一页  1  2  3  4  下一页>  余下全文
声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

人工智能 猎头职位 更多
扫码关注公众号
OFweek人工智能网
获取更多精彩内容
文章纠错
x
*文字标题:
*纠错内容:
联系邮箱:
*验 证 码:

粤公网安备 44030502002758号