一文搞懂多线程中各个难点
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,分别被谁拿着
最新活动更多
-
11月20日火热报名中>> 2024 智能家居出海论坛
-
11月28日立即报名>>> 2024工程师系列—工业电子技术在线会议
-
12月19日立即报名>> 【线下会议】OFweek 2024(第九届)物联网产业大会
-
即日-12.26火热报名中>> OFweek2024中国智造CIO在线峰会
-
即日-2025.8.1立即下载>> 《2024智能制造产业高端化、智能化、绿色化发展蓝皮书》
-
精彩回顾立即查看>> 【在线会议】多物理场仿真助跑新能源汽车
推荐专题
发表评论
请输入评论内容...
请输入评论/评论长度6~500个字
暂无评论
暂无评论