news 2026/3/14 9:55:51

Linux线程控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux线程控制

一、互斥:临界资源的排他性访问

1. 核心概念

互斥,即对临界资源的排他性访问,是多线程安全的基础。

  • 临界资源:多线程环境下,会被多个线程同时读写的资源,比如全局变量、文件句柄、硬件设备等。这类资源的读写操作不具备原子性,直接并发访问会导致数据一致性问题。
  • 排他访问:同一时刻,只能有一个线程对临界资源进行读写操作,其他线程必须等待,直到当前线程释放资源。

2. 为什么需要互斥?

以一个简单的A++操作为例,这个看似简单的语句,在汇编层面至少会被拆解为 3 步:

  1. 从内存中读取变量A的值到寄存器;
  2. 将寄存器中的值加 1;
  3. 将寄存器的值写回内存中的A

在多线程并发时,线程调度可能发生在任意步骤之间。比如线程th1执行完前两步后被切换,线程th2接着执行完整的三步,此时th1再切回继续执行第三步,就会覆盖th2的修改,最终导致数据错误。

互斥机制的作用,就是将这段非原子性的代码包裹为原子操作,确保其在一次线程调度中完整执行。

3. 互斥锁的使用步骤与核心 API

在 Linux 多线程编程中,互斥锁的核心数据结构是pthread_mutex_t,使用流程遵循定义→初始化→加锁→解锁→销毁的五步原则,每个步骤都对应明确的函数接口。

(1)定义互斥锁
#include <pthread.h> // 定义一个互斥锁变量(全局变量保证所有线程可见) pthread_mutex_t g_mutex;
(2)初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 功能:初始化已定义的互斥锁。
  • 参数
    • mutex:指向要初始化的互斥锁变量的指针;
    • attr:互斥锁属性,传入NULL表示使用默认属性。
  • 返回值:成功返回0,失败返回非零错误码。
(3)加锁操作
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 功能:对临界区代码加锁,加锁后的代码至解锁前的区域为原子操作,不允许线程调度打断。
  • 关键特性:如果互斥锁已被其他线程持有,当前线程会阻塞等待,直到锁被释放。
  • 参数mutex:指向已初始化的互斥锁指针。
  • 返回值:成功返回0,失败返回非零错误码。
(4)解锁操作
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 功能:释放持有的互斥锁,允许其他等待的线程获取锁并执行临界区代码。
  • 核心规则:加锁和解锁必须由同一个线程执行,不允许跨线程解锁。
  • 参数mutex:指向已加锁的互斥锁指针。
  • 返回值:成功返回0,失败返回非零错误码。
(5)销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 功能:互斥锁使用完毕后,释放其占用的系统资源。
  • 参数mutex:指向要销毁的互斥锁指针。
  • 返回值:成功返回0,失败返回非零错误码。
(6)互斥锁核心使用规范
  • 临界区代码要短小精悍:加锁后的代码执行时间越长,线程阻塞等待的时间就越久,会严重降低程序的并发效率。
  • 临界区内禁止休眠 / 大耗时操作:在临界区中调用sleep()read()/write()(大文件)等耗时操作,会导致锁被长时间持有,其他线程无法执行,完全丧失并发优势。
(7)互斥锁完整代码示例
#include <stdio.h> #include <pthread.h> #include <unistd.h> // 1. 定义互斥锁 pthread_mutex_t g_mutex; // 临界资源:全局计数器 int g_counter = 0; // 线程函数:对计数器累加 void *thread_func(void *arg) { int thread_id = *(int *)arg; for (int i = 0; i < 5; i++) { // 3. 加锁:pthread_mutex_lock int lock_ret = pthread_mutex_lock(&g_mutex); if (lock_ret != 0) { printf("线程%d 加锁失败!错误码:%d\n", thread_id, lock_ret); continue; } // 临界区(原子操作,保证同一时刻只有一个线程执行) // 遵循“短小精悍”原则:仅保留核心的临界资源操作 g_counter++; printf("线程%d 累加后:g_counter = %d\n", thread_id, g_counter); // 注意:此处若加sleep(1),会导致另一个线程长时间阻塞,违背互斥锁使用规范 // 4. 解锁:必须由当前加锁线程执行 int unlock_ret = pthread_mutex_unlock(&g_mutex); if (unlock_ret != 0) { printf("线程%d 解锁失败!错误码:%d\n", thread_id, unlock_ret); } sleep(1); // 非临界区可执行耗时操作 } return NULL; } int main() { pthread_t th1, th2; int id1 = 1, id2 = 2; // 2. 初始化互斥锁(默认属性) int init_ret = pthread_mutex_init(&g_mutex, NULL); if (init_ret != 0) { printf("互斥锁初始化失败!错误码:%d\n", init_ret); return -1; } // 创建两个线程 pthread_create(&th1, NULL, thread_func, &id1); pthread_create(&th2, NULL, thread_func, &id2); // 等待线程结束 pthread_join(th1, NULL); pthread_join(th2, NULL); // 5. 销毁互斥锁 int destroy_ret = pthread_mutex_destroy(&g_mutex); if (destroy_ret != 0) { printf("互斥锁销毁失败!错误码:%d\n", destroy_ret); return -1; } printf("最终计数器值:%d\n", g_counter); return 0; }
(8)运行结果说明

两个线程会交替对g_counter进行累加,最终结果稳定为10,不会出现数据不一致问题。如果去掉互斥锁,最终结果会小于10,且每次运行结果都不相同。

二、同步:线程的有序化执行

1. 核心概念

同步有先后顺序的排他性资源访问,它要求线程按照预定的逻辑顺序执行,本质上是互斥的一个特例。

比如生产消费模型中,必须保证生产者线程生产出数据后,消费者线程才能读取数据,这就是典型的同步场景。

2. 同步与互斥的核心区别

特性互斥锁信号量(同步)
核心目标排他性访问临界资源按顺序访问临界资源
锁 / 资源释放方加锁线程自己释放由其他线程交叉释放(th1 释放 th2,th2 释放 th1)
临界区限制禁止休眠、大耗时操作允许短时间休眠、小耗时操作
资源数量仅支持单一资源支持多资源(计数信号量)

3. 信号量:实现同步的核心工具

在 Linux 中,同步机制的实现通常依赖信号量,其核心数据结构是sem_t,使用流程为定义→初始化→PV 操作→销毁

(1)定义信号量
#include <semaphore.h> // 定义一个信号量变量 sem_t g_sem;
(2)初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 功能:初始化信号量,设置其共享属性和初始值。
  • 参数
    • sem:指向要初始化的信号量变量的指针;
    • pshared:共享属性,0表示线程间使用,非0表示进程间使用;
    • value:信号量初始值,0表示无资源(线程阻塞),1表示单资源(二值信号量),大于1表示多资源(计数信号量)。
  • 返回值:成功返回0,失败返回-1
(3)信号量的 PV 操作

信号量的核心操作是P 操作(申请资源)V 操作(释放资源),这两个操作都是原子操作,对应两个核心函数。

P 操作:申请资源(sem_wait)
int sem_wait(sem_t *sem);
  • 功能:尝试申请信号量资源,执行sem = sem - 1
    • 若操作后sem >= 0,线程继续执行;
    • 若操作后sem < 0,线程阻塞等待,直到有其他线程释放资源。
  • 参数sem:指向已初始化的信号量指针。
  • 返回值:成功返回0,失败返回-1
V 操作:释放资源(sem_post)
int sem_post(sem_t *sem);
  • 功能:释放信号量资源,执行sem = sem + 1
  • 核心规则:由目标线程的 “依赖线程” 交叉释放(如消费者释放生产者、生产者释放消费者)。
  • 关键特性:线程执行该函数时不会阻塞,释放后会唤醒等待该信号量的线程。
  • 参数sem:指向已初始化的信号量指针。
  • 返回值:成功返回0,失败返回-1
(4)销毁信号量
int sem_destroy(sem_t *sem);
  • 功能:释放信号量占用的系统资源。
  • 参数sem:指向要销毁的信号量指针。
  • 返回值:成功返回0,失败返回-1
(5)计数信号量的特殊用法

信号量初值可设置为大于 1 的数值(如 3、5),适用于多资源互斥场景(资源数本身不唯一)。例如:

  • 初始化信号量sem_init(&sem, 0, 3),表示同时允许 3 个线程访问临界资源;
  • 每个线程执行sem_wait()申请资源,sem_post()释放资源;
  • 当第 4 个线程执行sem_wait()时,会阻塞等待前 3 个线程中任意一个释放资源。
(6)信号量核心使用规范
  • 允许短时间休眠 / 小耗时操作:信号量的核心目标是保证线程执行顺序,而非极致的并发效率,因此临界区中可执行sleep(1)等短耗时操作;
  • 交叉释放规则:同步场景下,信号量的 PV 操作需由不同线程交叉执行(如生产者 V 操作释放消费者,消费者 V 操作释放生产者)。
(7)信号量完整代码示例(生产消费模型)
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> // 定义信号量:控制生产消费顺序 sem_t g_sem_producer; // 生产者信号量 sem_t g_sem_consumer; // 消费者信号量 // 临界资源:产品缓冲区 int g_product = 0; // 生产者线程函数 void *producer_func(void *arg) { for (int i = 0; i < 5; i++) { // P操作:申请生产者资源(初始值为1,可直接执行) sem_wait(&g_sem_producer); // 生产产品(允许短耗时操作) g_product = i + 1; printf("生产者生产产品:%d\n", g_product); sleep(1); // 模拟生产耗时(信号量场景下允许) // V操作:释放消费者资源(交叉释放,让消费者可以消费) sem_post(&g_sem_consumer); } return NULL; } // 消费者线程函数 void *consumer_func(void *arg) { for (int i = 0; i < 5; i++) { // P操作:申请消费者资源(初始值为0,阻塞等待生产者释放) sem_wait(&g_sem_consumer); // 消费产品(允许短耗时操作) printf("消费者消费产品:%d\n", g_product); sleep(1); // 模拟消费耗时(信号量场景下允许) // V操作:释放生产者资源(交叉释放,让生产者可以继续生产) sem_post(&g_sem_producer); } return NULL; } int main() { pthread_t th_producer, th_consumer; // 初始化信号量 // 生产者信号量初始值1:允许先生产 sem_init(&g_sem_producer, 0, 1); // 消费者信号量初始值0:必须等生产后才能消费 sem_init(&g_sem_consumer, 0, 0); // 创建线程 pthread_create(&th_producer, NULL, producer_func, NULL); pthread_create(&th_consumer, NULL, consumer_func, NULL); // 等待线程结束 pthread_join(th_producer, NULL); pthread_join(th_consumer, NULL); // 销毁信号量 sem_destroy(&g_sem_producer); sem_destroy(&g_sem_consumer); return 0; }
(8)计数信号量示例(多资源访问)
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> // 定义计数信号量:初始值3,允许3个线程同时访问 sem_t g_count_sem; // 临界资源:资源使用计数 int g_res_used = 0; void *thread_func(void *arg) { int thread_id = *(int *)arg; // P操作:申请资源 sem_wait(&g_count_sem); g_res_used++; printf("线程%d 占用资源,当前使用数:%d\n", thread_id, g_res_used); sleep(2); // 模拟小耗时操作 // 释放资源 g_res_used--; printf("线程%d 释放资源,当前使用数:%d\n", thread_id, g_res_used); // V操作:释放资源 sem_post(&g_count_sem); return NULL; } int main() { pthread_t th[5]; int ids[5] = {1,2,3,4,5}; // 初始化计数信号量:允许3个线程同时访问 sem_init(&g_count_sem, 0, 3); // 创建5个线程 for (int i = 0; i < 5; i++) { pthread_create(&th[i], NULL, thread_func, &ids[i]); } // 等待所有线程结束 for (int i = 0; i < 5; i++) { pthread_join(th[i], NULL); } sem_destroy(&g_count_sem); return 0; }
(9)运行结果说明
  • 生产消费模型:严格按照生产→消费→生产→消费的顺序执行;
  • 计数信号量模型:同一时刻最多有 3 个线程占用资源,第 4、5 个线程会阻塞,直到前 3 个线程释放资源。

三、死锁:多线程编程的 “隐形陷阱”

1. 死锁的概念

死锁是指由于锁资源的申请和释放逻辑不合理,导致多个线程互相等待对方持有的锁,最终所有线程都无法继续执行的现象。

比如线程 A 持有锁 1,等待锁 2;线程 B 持有锁 2,等待锁 1,此时两个线程会永远阻塞,程序陷入停滞。

2. 死锁产生的四个必要条件

死锁的发生必须同时满足以下四个条件,只要破坏其中任意一个,就能避免死锁。

  1. 互斥条件:一个资源每次只能被一个线程使用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源,在未使用完毕前,不能被强行剥夺。
  4. 循环等待条件:若干线程之间形成头尾相接的循环等待资源关系。

3. 死锁的规避思路

  • 固定顺序申请锁:所有线程都按照相同的顺序获取多个锁,避免循环等待。比如线程 A 和线程 B 都先获取锁 1,再获取锁 2。
  • 锁的申请时限:使用pthread_mutex_trylock()尝试加锁,设置超时时间,超时后放弃申请并释放已持有的锁。
  • 减少锁的嵌套:尽量避免一个临界区内部再申请其他锁,降低锁依赖的复杂度。
  • 资源一次性申请:在线程执行初期,一次性申请所有需要的锁,避免中途申请新锁。

四、总结

在 Linux 多线程编程中,互斥锁解决了临界资源的排他性访问问题,保证了数据一致性,核心规则是 “加解锁同线程、临界区短小精悍”;信号量在此基础上实现了线程的有序执行,核心规则是 “交叉释放资源、允许短耗时操作”,计数信号量还可适配多资源互斥场景。而死锁作为多线程编程的常见问题,需要我们通过规范锁的使用逻辑来规避。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 5:05:48

永磁同步电机MotorCAD仿真详细流程揭秘

某永磁同步电机motorcad仿真流程,很详细 录制video文档最近在研究永磁同步电机的相关内容&#xff0c;发现MotorCAD这个软件在永磁同步电机仿真方面真的非常强大。今天就来给大家分享一下永磁同步电机MotorCAD的详细仿真流程&#xff0c;同时我还录制了配套的video&#xff0c;…

作者头像 李华
网站建设 2026/3/11 15:28:11

跳出“要么稳要么冲”陷阱:广告预算的确定性与增长性双驱法则

在亚马逊运营中&#xff0c;广告预算分配是一场精密的资源调度艺术&#xff0c;如何在“确保盈利”的确定性与“追求增长”的探索性之间找到平衡&#xff0c;是卖家必须掌握的核心能力。一、锚定底层逻辑&#xff1a;不同生命周期的预算哲学广告预算的设定&#xff0c;必须始于…

作者头像 李华
网站建设 2026/3/13 8:39:21

上海交大造出手机AI助手ColorAgent:不只是工具,更像贴心伙伴

这项突破性研究由上海交通大学与OPPO研究院联合完成&#xff0c;研究成果发表于2025年10月22日的arXiv预印本平台&#xff0c;论文编号为arXiv:2510.19386v1。研究团队由来自上海交通大学的李宁、吴正、张伟明等多位学者&#xff0c;以及OPPO研究院的林旗强、莫晓芸、赵音等专家…

作者头像 李华
网站建设 2026/3/10 13:39:58

机器视觉介绍

机器视觉的定义机器视觉&#xff08;Machine Vision&#xff09;是指通过计算机和图像处理技术模拟人类视觉功能&#xff0c;实现对物体识别、测量、定位和分析的自动化系统。广泛应用于工业检测、自动驾驶、医疗影像等领域。机器视觉的核心技术图像采集 通过摄像头、工业相机或…

作者头像 李华
网站建设 2026/3/13 13:19:07

基于鲹鱼优化算法(GTO)优化Canopy聚类附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知,完整Matlab代码获取及仿…

作者头像 李华
网站建设 2026/3/4 22:47:45

IMU和GPS ekf融合定位 从matlab到c++代码实现 基于位姿状态方程

IMU和GPS ekf融合定位 从matlab到c代码实现 基于位姿状态方程&#xff0c;松耦合 文档且详细 蹲在实验室捯饬了三天咖啡机之后&#xff0c;我终于把IMU和GPS的EKF融合算法从Matlab搬到了C。这事儿就像把乐高积木从说明书模式切换到自由创作模式——你知道原理是对的&#xff0…

作者头像 李华