云计算百科
云计算领域专业知识百科平台

Linux入门DAY31(线程分离属性、互斥同步、死锁详解)

#Linux #C语言 #多线程 #嵌入式开发 #互斥锁

前言

本篇接续前面 Linux 进程线程知识点,重点讲解线程分离 / 非分离属性、pthread_join 回收、互斥锁、信号量同步、死锁产生条件以及规避方案,是多线程开发高频考点与工程实践必掌握内容。编译线程代码务必增加编译链接参数:-pthread。

一、线程的分离属性与非分离属性

线程有两种属性:非分离属性(默认)、分离属性,决定线程结束后资源由谁回收。

1. 非分离属性(系统默认)

  • 线程结束之后,线程的栈、线程描述符等资源不会自动释放,会残留在内核中,造成资源泄露。
  • 必须由另外一个线程调用 pthread_join() 进行阻塞回收资源。

int pthread_join(pthread_t thread, void **retval);

参数说明

  • thread:要回收的线程 ID
  • retval:接收线程退出返回值;不关心返回结果直接填 NULL

返回值:成功返回 0;失败返回非 0 错误码。

特点:调用 pthread_join 的线程会阻塞等待目标线程执行完毕,目标线程不结束,join 函数不会返回。适合需要等待子线程执行完再继续业务逻辑的场景。

2. 分离属性

设置成分离属性之后:线程执行完毕退出时,操作系统自动回收该线程全部资源,不需要、也不能调用 pthread_join 去回收。

int pthread_detach(pthread_t tid);

  • tid:需要设置分离属性的线程 ID
  • 返回值:成功 0,失败非 0。

✅ 使用场景:主线程不需要等待子线程完成,子线程独立执行任务,不需要获取返回值。

注意:一个线程不能既被 detach 分离,又调用 pthread_join,会报错。

线程退出的三种方式

  • 线程任务函数内部使用 return;
  • 调用 pthread_exit(void *retval) 主动退出当前线程;
  • 进程整体退出,该进程内所有线程全部直接销毁。
  • ⚠️坑点:如果 main 主线程return 0直接退出,整个进程销毁,还在运行的子线程会直接被杀死,来不及完成业务。

    二、线程临界资源与竞态问题

    临界资源

    多个线程都可以访问的公共资源,例如:全局变量、全局队列、公共文件、数据库。

    临界区

    访问临界资源的那一段代码块。

    多线程同时读写临界资源会发生数据竞争(竞态):多个线程同时读改写同一个变量,运算结果错乱。 示例:两个线程同时对同一个全局变量做自增,预期累加 10000,实际结果远小于预期。

    解决竞争问题:互斥锁保证同一时间,只有一个线程进入临界区执行。

    三、互斥锁 pthread_mutex_t(线程互斥)

    互斥锁作用:实现线程互斥访问临界资源,保证临界区代码原子执行。

    互斥锁完整流程

  • 定义锁对象:pthread_mutex_t mutex;
  • 初始化互斥锁 pthread_mutex_init()
  • 访问临界区前:pthread_mutex_lock()【阻塞加锁】
  • 执行业务临界区代码
  • 访问完成:pthread_mutex_unlock() 解锁
  • 锁不再使用:pthread_mutex_destroy()销毁锁
  • //初始化锁
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    const pthread_mutexattr_t *restrict attr);
    //attr填NULL代表使用锁的默认属性

    函数说明:

  • pthread_mutex_lock():阻塞加锁。如果锁已经被别的线程占用,当前线程休眠阻塞,直到锁被释放。
  • pthread_mutex_unlock():释放互斥锁。
  • pthread_mutex_destroy():销毁锁,释放锁占用的内核资源。
  • 非阻塞尝试加锁

    int pthread_mutex_trylock(pthread_mutex_t *mutex);

    如果锁已经被占用,函数不会阻塞休眠,直接返回错误,线程可以去做其他业务。可以用来规避死锁。

    其他锁简单了解

  • 读写锁:读共享,写独占。多个线程可以同时读;写操作只能独占。适合读多写少场景。
  • 自旋锁:拿不到锁的时候不会休眠,CPU 空循环轮询抢占锁。CPU 消耗高,适合临界区代码执行极短的场景。
  • 四、信号量 sem_t(线程同步)

    互斥:解决 “同一时间只能一个人访问资源”;同步:解决 “线程执行先后顺序”。

    信号量本质代表可用资源的计数。

    • P 操作(sem_wait):申请资源,资源计数减一。资源为 0 的时候线程阻塞等待资源。
    • V 操作(sem_post):释放资源,资源计数加一,唤醒等待该信号量的线程。

    信号量 API

    //初始化信号量
    int sem_init(sem_t *sem, int pshared, unsigned int value);

    参数:

    • sem:信号量对象地址
    • pshared:0 代表线程之间共享;非 0 代表进程间共享
    • value:信号量初始资源数量

    int sem_wait(sem_t *sem); //P操作,申请资源,计数‑1,资源0则阻塞
    int sem_post(sem_t *sem); //V操作,释放资源,计数+1,唤醒阻塞线程
    int sem_destroy(sem_t *sem); //销毁信号量

    五、死锁产生的条件以及解决方案

    什么是死锁

    多线程环境,多个线程各自持有已经获取的锁,同时互相请求对方手里持有的锁,所有线程永久阻塞,程序卡死。

    典型场景:

    线程 A 拿到锁 1,等待锁 2;线程 B 拿到锁 2,等待锁 1;两个线程永远卡住。

    死锁四个必要条件(必须全部满足,才会发生死锁,破坏任意一个条件就可以解除死锁)

  • 互斥条件:资源(锁)同一时刻只能被一个线程占有。
  • 请求与保持条件:线程已经占有部分锁不释放,同时继续申请其它锁。
  • 不剥夺条件:锁资源只能由持有锁的线程主动释放,其它线程不能强行抢夺锁。
  • 循环等待条件:多个线程之间形成环路,互相等待对方手中资源。
  • 工程上避免死锁的手段

  • 锁成对使用:lock之后一定要配套unlock,分支、异常路径也要保证解锁。
  • 统一所有线程加锁顺序:所有线程申请多把锁时,都按照固定顺序申请,破坏循环等待。
  • 使用非阻塞锁 pthread_mutex_trylock,获取锁失败时主动释放自身已经持有的锁。
  • 尽量减少同时持有多把锁的业务逻辑。
  • 小结

  • 默认线程是非分离属性,需要pthread_join回收;detach 设置分离属性由系统自动回收资源。
  • 临界资源会带来竞态问题,互斥锁保证临界区互斥访问;trylock 实现非阻塞加锁。
  • 信号量主要用来做线程同步,P 操作申请、V 操作释放资源。
  • 死锁四个必要条件,开发中重点保证加锁顺序一致,防止死锁。
  • 🔔 补充:线程和进程对比回顾

    • 进程:资源分配最小单位;内存空间独立,IPC 通信,开销大。
    • 线程:CPU 调度最小单位;共享进程堆、全局变量,仅栈独立,开销小,容易出现资源竞争。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux入门DAY31(线程分离属性、互斥同步、死锁详解)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!