文章目录
- 前言
- 线程互斥
- Q:什么是临界资源?临界区呢?
- Q:什么是互斥?
- Q:数据不一致的本质是什么?
- Q:用锁对共享资源进行保护的前提是:锁也要作为共享资源被其他线程使用。那么用锁保护共享资源的本质是:用共享资源保护共享资源,这合理吗?
- Q:lock的原子操作是怎样实现的?
- Q:死锁的四个必要条件是什么?如何避免死锁?
- 可重入与线程安全
- Q:什么是可重入?它与线程安全的关系是什么?
前言
本篇博客梳理关于线程相关的Q&A,主要关注访问共享资源时,涉及到的线程互斥性。若读者也在复习这块知识,或者正在学习这块知识,可以通过这些Q&A检测自己的知识掌握情况。此外,思维导图已经更新至我的gitee,Q&A之外的体系梳理还请移步思维导图。
线程互斥
Q:什么是临界资源?临界区呢?
A:由于进程下的线程共享同一进程地址空间,对于进程下的共享资源,每个线程都可以低成本的访问。但由于线程并发的存在,多线程同时访问共享资源可能造成数据不一致的问题,进而导致严重的bug。而解决这一问题的方法是:将共享资源转换成临界资源,当一个线程访问临界资源时,其他线程无法访问。也就是说,临界资源是同一时刻只允许一个线程访问的资源。而访问临界资源的代码块就被成为临界区。
Q:什么是互斥?
A:同一时刻只允许一个线程访问的资源,我们称该资源具有互斥性。临界资源具有互斥性。
Q:数据不一致的本质是什么?
A:线程访问共享资源时,相关操作不是原子操作!也就是不具有原子性。线程对共享资源的操作执行一半时,就被操作系统切走,在该线程重新被调度之前,其他线程对该共享资源的操作无效。
Q:用锁对共享资源进行保护的前提是:锁也要作为共享资源被其他线程使用。那么用锁保护共享资源的本质是:用共享资源保护共享资源,这合理吗?
A:首先,锁确实是一个共享资源。但是多线程并发访问共享资源带来的数据不一致问题的本质原因不是:该资源是否是共享资源,而是:访问共享资源的操作是否具有原子性!我们对mutex的所有操作都是原子的,即使mutex是一个共享资源,但原子操作可以保证其数据一致,不会出现错误。也就是说,pthread线程库将所有mutex的操作设计成原子操作,以保证mutex的绝对安全。
Q:lock的原子操作是怎样实现的?
A:
这是上锁过程的伪代码。简单理解一下代码
- movb $0, %al:将0加载到al寄存器中
- xchgb %al, mutex:将al寄存器的内容与mutex交换。mutex是锁的一个变量,其值为1
- 判断al寄存器的值是否为0。
- 若不为0,则lock成功
- 若为0,则lock失败,线程被挂起
- 当线程被唤醒后,会重新执行一次lock
其中的关键是xchgb这条指令,这是一个由内核实现的原子操作,也就是说交换过程要么已经完成,要么没有发生。正是xchgb的原子性,整个lock接口也具有了原子性。
一把互斥锁只有一个值为1的mutex变量,线程想要上锁就要加载0到寄存器,将0和mutex进行交换。而0很廉价,每个线程都能有0,都拿着0与互斥锁的1进行交换,但是一旦1被某个线程交换走,其他线程交换得到的就是被用来交换的0了。而只有交换得到1的线程才能lock成功,其他线程会陷入阻塞,并且被唤醒后会重复这一过程(直到al寄存器交换到1为止)。
这就是lock上锁的过程,其中有一个问题,如果al寄存器一直交换到0,那么调用lock的线程就会不停的重复这个过程。这样的现象叫做死锁。
Q:死锁的四个必要条件是什么?如何避免死锁?
A:
- 互斥条件:多线程并发访问共享资源,但同一时刻只允许一个线程访问临界资源
- 不剥夺条件:除非持有锁的线程自己释放锁,否则其他线程无法剥夺该线程持有的锁
- 请求与保持条件:持有锁的线程继续请求其他锁资源,若因此陷入阻塞,其他线程一定无法获取其持有的锁资源
- 循环等待条件:线程之间请求锁的请求关心构成循环。最简单的循环就是A和B两个线程同时请求对方持有的锁资源,此时双方都陷入阻塞,构成了死锁
- 破坏以上四种关系中的任意一种即可避免死锁,一般情况下,我们只能破坏条件三和条件四
- 加锁顺序一致与资源一次性分配:破坏循环等待条件,减小线程占用彼此锁资源的概率
- 及时释放锁资源:破坏请求与保持条件。加锁的粒度越细越好,这不仅是为了提高程序的运行效率,也是为了避免死锁
可重入与线程安全
Q:什么是可重入?它与线程安全的关系是什么?
A:可重入是指:可以在被中断后继续执行(重复进入),并且不会发生错误的函数或者代码段。比如线程A正在调用某一函数或者代码段,调用到一半被cpu切走,此时线程B也调用了该函数(也可以不调用该函数,只是正常的运行)。当cpu恢复线程A时,线程A继续执行代码且执行完成后,程序不会发生错误(数据不一致,内存泄漏),此时我们称该函数或者代码段具有可重入性。
线程安全是指:在多线程并发的场景下,程序不会出现数据/结果不一致的情况。这些情况多发生在线程访问静态变量与全局变量(共享资源)时。
可重入的函数/代码段一定是线程安全的,但线程安全的程序中,函数/代码段不一定是可重入的,因为它可能添加了访问控制。
需要注意的是:可重入与不可重入没有优劣之分,它们只是区分代码的两个性质。若要实现线程安全,可以对不可重入的代码添加访问控制。本质就是建立临界区,保护共享资源。