面试 : Linux 上的锁

Linux 中程序使用的锁

不要和我说什么原子操作,加一把大锁就完了。什么缓存过期内存延时,光速不够快赖我?

出题篇

最近在跑各种厂商的C/C++后端面试,其中一个特别高频的考点就是程序中的死锁问题,主要的问题表现形式如下:

  1. 如果程序中出现了死锁,除了程序会卡住之外,在操作系统上还会有什么表现形式?

  2. 承接上一个题目,出现死锁之后各个系统资源的占用如何?

  3. 如何定位到是哪里出现了死锁?

  4. 使用top命令查看进程处于 Sleep 状态下, 一定会让出CPU吗

还好曾经在写代码的时候脑子抽了写过死锁,这几个问题勉强答出来了

解答篇

出现死锁后的状况,问题的核心在于使用什么锁。在大量使用睡眠等待锁的今天,死锁造成的性能损失几乎可以忽略不记了,不过自旋锁可就没这么好心的让出资源。

什么,你说你消费者线程被阻塞住,生产者还在发消息?

祝任务队列 / 消息队列 早日 OOM

出现死锁后的情况 mutex

首先先构造一个简单的含有死锁的两个线程的程序, 两个线程持有一个锁并互相等待对方的锁。

thread0 获取 mutex0 等待 mutex1

thread1 获取 mutex1 等待 mutex0

#include<mutex>
#include<thread>
#include<iostream>

void mutex_test(){
    std::mutex test_mutex0;
    std::mutex test_mutex1;

    std::thread thread0(
        [&test_mutex0, &test_mutex1](){
            while(true){
                test_mutex0.lock();
                std::cout << "thread 0 get mutex 0" << std::endl;
                std::this_thread::sleep_for(std::chrono::seconds(5));
                test_mutex1.lock();
                std::cout << "thread 0 get mutex 1" << std::endl;
                test_mutex1.unlock();
                test_mutex0.unlock();
                break;
            }
        }
    );

    std::thread thread1(
        [&test_mutex0, &test_mutex1](){
            while(true){
                test_mutex1.lock();
                std::cout << "thread 1 get mutex 1" << std::endl;
                std::this_thread::sleep_for(std::chrono::seconds(5));
                test_mutex0.lock();
                std::cout << "thread 1 get mutex 0" << std::endl;
                test_mutex0.unlock();
                test_mutex1.unlock();
                break;
            }
        }
    );

    thread0.join();
    thread1.join();
}

int main(){
    mutex_test();
}

注意到该程序中使用的是 mutex 锁,该锁在阻塞的情况下会睡眠等待,因此此时该程序占用的系统资源几乎没有,虽然程序卡住了但操作系统不会卡住,也不会拖累操作系统的速度;

可以使用 ps 命令查看卡住进程的 pid ,然后使用 top 命令查看该 pid 的系统资源占用情况。

什么?ps 命令不知道该看哪个程序?程序是人工手动启动的这能不知道过滤条件吗?

  1. 程序卡住的截图

img

  1. 利用 ps -aux | grep lock 命令查看 pid,因为使用了 grep 命令所以就没显示表头

img

才意识到 top 命令中就能看到这个程序的各个状态。

pid 进程状态
5601 SL+(睡眠S 且为多线程L)

能拿到 pid 就可以请出来 gdb 这尊大佛了,我项目中使用最多的也是 gdb

img

进了 gdb 那可就是连底裤都能看出来,info threads 查看各个线程的标号,然后 bt 查看函数调用栈即可;在这里可以看到当前线程运行到了 mutex_test() 函数的 lambda 函数中,再之后就需要手动的去排查代码中该 lambda 表达式出现了什么问题

img

出现死锁的情况 spin_lock

这里插播一条消息,Linux中自旋锁的设计不是单纯的一个原子的bool就能实现的,需要考虑到很多问题,比如:

  1. 多核 CPU 的系统中,变量所在的内存物理位置距离不同的CPU距离不同,会导致不同的线程抢占的公平性不同

  2. 新来的线程可能更容易抢占到锁,牢线程自旋到饿死

这里不展开自旋锁的设计,可以参考该文章:

https://zhuanlan.zhihu.com/p/18712936348

这个自旋锁的程序可就不这么友好了;

img

虽然我显示的是 SL+ ,我在 sleep ,但是忙等待,自旋锁让出CPU很短的时间后,回来抢占CPU检查状态

死锁代码和上面类似,只不过是使用的自旋锁

#include <pthread.h>
#include <iostream>
#include <thread>

void spin_test() {
  pthread_spinlock_t lock0;
  pthread_spinlock_t lock1;
  pthread_spinlock_t* ptr_lock0 = &lock0;
  pthread_spinlock_t* ptr_lock1 = &lock1;
  pthread_spin_init(&lock0, 0);
  pthread_spin_init(&lock1, 0);

  std::thread thread0([ptr_lock0, ptr_lock1]() {
    pthread_spin_lock(ptr_lock0);
    std::cout << "thread 0 get mutex 0" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    pthread_spin_lock(ptr_lock1);
    std::cout << "thread 0 get mutex 1" << std::endl;
    pthread_spin_unlock(ptr_lock1);
    pthread_spin_unlock(ptr_lock0);
  });

  std::thread thread1([ptr_lock0, ptr_lock1]() {
    pthread_spin_lock(ptr_lock1);
    std::cout << "thread 1 get mutex 1" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    pthread_spin_lock(ptr_lock0);
    std::cout << "thread 1 get mutex 0" << std::endl;
    pthread_spin_unlock(ptr_lock0);
    pthread_spin_unlock(ptr_lock1);
  });

  thread0.join();
  thread1.join();
}

int main(){
    spin_test();
}
posted @ 2025-03-26 11:52  PolarisZg  阅读(60)  评论(0)    收藏  举报