面试官让你讲讲Linux内核的竞争与并发,你该如何回答?( 二 )


面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
要C/C++ Linux服务器架构师学习资料关注获取(资料包括C/C++ , Linux , golang技术 , Nginx , ZeroMQ , MySQL , Redis , fastdfs , MongoDB , ZK , 流媒体 , CDN , P2P , K8S , Docker , TCP/IP , 协程 , DPDK , ffmpeg等) , 免费分享自旋锁简介??自旋锁 , 顾名思义 , 我们可以把他理解成厕所门上的一把锁 。 这个厕所门只有一把钥匙 , 当我们进去时 , 把钥匙取下来 , 进去后反锁 。 那么当第二个人想进来 , 必须等我们出去后才可以 。 当第二个人在外面等待时 , 可能会一直等待在门口转圈 。
??我们的自旋锁也是这样 , 自旋锁只有锁定和解锁两个状态 。 当我们进入拿上钥匙进入厕所 , 这就相当于自旋锁锁定的状态 , 期间谁也不可以进来 。 当第二个人想要进来 , 这相当于线程B想要访问这个共享资源 , 但是目前不能访问 , 所以线程B就一直在原地等待 , 一直查询是否可以访问这个共享资源 。 当我们从厕所出来后 , 这个时候就“解锁”了 , 只有再这个时候线程B才能访问 。
??假如 , 在厕所的人待的时间太长怎么办?外面的人一直等待吗?如果换做是我们 , 肯定不会这样 , 简直浪费时间 , 可能我们会寻找其他方法解决问题 。 自旋锁也是这样的 , 如果线程A持有自旋锁时间过长 , 显然会浪费处理器的时间 , 降低了系统性能 。 我们知道CPU最伟大的发明就在于多线程操作 , 这个时候让线程B在这里傻傻的不知道还要等待多久 , 显然是不合理的 。 因此 , 如果自旋锁只适合短期持有 , 如果遇到需要长时间持有的情况 , 我们就要换一种方式了(下文的互斥体) 。
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
自旋锁是主要为了多处理器系统设计的 。 对于单处理器且内核不支持抢占的系统 , 一旦进入了自旋状态 , 则会永远自旋下去 。 因为 , 没有任何线程可以获取CPU来释放这个锁 。 因此 , 在单处理器且内核不支持抢占的系统中 , 自旋锁会被设置为空操作 。
??以上列表中的函数适用于SMP或支持抢占的单CPU下线程之间的并发访问 , 也就是用于线程与线程之间 , 被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞(其实本质仍然是睡眠)的API函数 , 否则的话会可能会导致死锁现象的发生 。 自旋锁会自动禁止抢占 , 也就说当线程A得到锁以后会暂时禁止内核抢占 。 如果线程A在持有锁期间进入了休眠状态 , 那么线程A会自动放弃CPU使用权 。 线程B开始运行 , 线程B也想要获取锁 , 但是此时锁被A线程持有 , 而且内核抢占还被禁止了!线程B无法被调度岀去 , 那么线程A就无法运行 , 锁也就无法释放死锁发生了!
??当线程之间发生并发访问时 , 如果此时中断也要插一脚 , 中断也想访问共享资源 , 那该怎么办呢?首先可以肯定的是 , 中断里面使用自旋锁 , 但是在中断里面使用自旋锁的时候 , 在获取锁之前一定要先禁止本地中断(也就是本CPU中断 , 对于多核SOC来说会有多个CPU核) , 否则可能导致锁死现象的发生 。 看下下面一个例子:
//线程Aspin_lock(.......functionA();.......spin_unlock(//中断发生 , 运行线程Bspin_lock(.......functionA();.......spin_unlock(??线程A先运行 , 并且获取到了lock这个锁 , 当线程A运行 functionA函数的时候中断发生了 , 中断抢走了CPU使用权 。 下边的中断服务函数也要获取lock这个锁 , 但是这个锁被线程A占有着 , 中断就会一直自旋 , 等待锁有效 。 但是在中断服务函数执行完之前 , 线程A是不可能执行的 , 线程A说“你先放手” , 中断说“你先放手” , 场面就这么僵持着死锁发生!