news 2026/4/22 17:15:21

Linux系统编程——线程控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux系统编程——线程控制

目录

一、临界资源与线程安全问题

二、互斥:让临界区 “独占” 执行

1.互斥锁的原理

2.互斥锁的使用步骤(pthread 库)

2.1 定义互斥锁

2.2 初始化互斥锁

2.3 加锁(进入临界区)

2.4 解锁(离开临界区)

2.5 销毁互斥锁

3.互斥锁实战示例

三、同步:让线程 “按顺序” 执行

1. 同步与互斥的关系

2.信号量的原理

3.信号量的使用步骤

3.1 定义信号量

3.2 初始化信号量

3.3 信号量的 PV 操作

3.4 销毁信号量

4.信号量同步实战示例

四、死锁


一、临界资源与线程安全问题

  • 临界资源:在线程间会被读写操作的资源(比如全局变量、文件、硬件设备)。
  • 线程安全问题:多个线程 “穿插执行” 临界资源的操作时,会破坏数据一致性。

举个例子:A++看似是一行代码,但编译后会分成 3 步(读 A→A+1→写回 A)。如果线程 1 执行到 “读 A” 后被切换到线程 2,线程 2 也执行A++,最终 A 的值会比预期小 —— 这就是数据竞争

二、互斥:让临界区 “独占” 执行

互斥的核心是排他性访问:同一时刻,只有一个线程能操作临界资源。

1.互斥锁的原理

通过 “锁” 来保护临界区代码(操作临界资源的代码):

  • 线程要执行临界区,必须先 “加锁”;
  • 锁被占用时,其他线程会阻塞等待;
  • 线程执行完临界区,必须 “解锁”,让其他线程可以竞争锁。

th1、th2是并发运行的两个线程。也就是代码在运行时,th1与th2是穿插进行的。

2.互斥锁的使用步骤(pthread 库)

Linux 下用 pthread_mutex_t 实现互斥锁,步骤是:定义→初始化→加锁→解锁→销毁

2.1 定义互斥锁

#include <pthread.h> // 定义全局/共享的互斥锁 pthread_mutex_t mutex;

2.2 初始化互斥锁

// 函数原型 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); // 示例 pthread_mutex_init(&mutex, NULL);
  • 功能:将已经定义好的互斥锁初始化。
  • 参数:mutex 是要初始化的锁;attr 传NULL表示用默认属性。
  • 返回值:成功返回 0,失败返回非 0。

2.3 加锁(进入临界区)

// 函数原型 int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 功能:用指定的互斥锁开始加锁代码,成功则进入临界区;失败则阻塞等待。
  • 注意:加锁后的代码是原子操作(线程调度不会打断这段代码)。

2.4 解锁(离开临界区)

// 函数原型 int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 功能:将指定的互斥锁解锁,让其他线程可以竞争。解锁之后代码不再排他访问。
  • 注意:加锁和解锁必须成对出现,且要在同一个线程中执行。

2.5 销毁互斥锁

// 函数原型 int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 功能:释放互斥锁的资源,锁不再使用时调用。

3.互斥锁实战示例

比如两个线程同时对全局变量 A 做 ++ 操作,用互斥锁保证线程安全:

#include <pthread.h> #include <stdio.h> int A = 0; pthread_mutex_t mutex; // 线程函数 void* th(void* arg) { int i = 5000; while (i--) { // 加锁:进入临界区 pthread_mutex_lock(&mutex); int tmp = A; printf("A is %d\n", tmp + 1); // 循环输出到 A is 10000 A = tmp + 1; // 解锁:离开临界区 pthread_mutex_unlock(&mutex); } return NULL; } int main(int argc, char** argv) { pthread_t tid1, tid2; // 初始化互斥锁 pthread_mutex_init(&mutex, NULL); pthread_create(&tid1, NULL, th, NULL); pthread_create(&tid2, NULL, th, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 销毁互斥锁 pthread_mutex_destroy(&mutex); return 0; }

三、同步:让线程 “按顺序” 执行

互斥解决了 “资源独占”,但没解决 “执行顺序”。同步是让线程按预先约定的顺序执行(比如 “线程 1 输出 Hello 后,线程 2 再输出 World”)。

1. 同步与互斥的关系

  • 同步是互斥的 “特例”:同步不仅要排他访问,还要控制执行顺序。

  • 实现同步的工具:信号量(可以理解为 “带计数的锁”)。
    • 互斥锁:加锁和解锁是同一个线程,临界区代码短小精悍,避免休眠、大耗时的操作
    • 信号量:th1 释放 th2,th2 释放 th1。由线程交叉释放。可以有适当休眠、小的耗时操作

2.信号量的原理

信号量是一个整数 sem,通过 P 操作(申请资源)和 V 操作(释放资源)实现同步:

  • P 操作:sem--,若 sem<0 则线程阻塞;
  • V 操作:sem++,若 sem<=0 则唤醒一个阻塞的线程。

注:Linux 下用 sem_t 实现信号量

3.信号量的使用步骤

步骤是:定义→初始化→PV 操作→销毁

3.1 定义信号量

#include <semaphore.h> sem_t sem;

3.2 初始化信号量

// 函数原型 int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 参数:
    • sem:要初始化的信号量;
    • pshared:0表示线程间共享,非 0 表示进程间共享;
    • value:信号量初始值(比如 0 表示 “无资源”,1 表示 “有 1 个资源”)。
  • 返回值:成功返回 0,失败返回 -1。

3.3 信号量的 PV 操作

  • P 操作(申请资源):对应sem_wait()
int sem_wait(sem_t *sem);

功能:判断当前 sem 信号量是否有资源可用。

如果 sem 有资源 (==1),则申请该资源,程序继续运行;

如果 sem 没有资源 (==0),则线程阻塞等待,一旦有资源则自动申请资源并继续运行程序。

  • V 操作(释放资源):对应sem_post()
int sem_post(sem_t *sem);

功能:函数可以将指定的 sem 信号量资源释放,并默认执行 sem = sem+1。

线程在该函数上不会阻塞。

3.4 销毁信号量

int sem_destroy(sem_t *sem);

功能:使用完毕将指定的信号量销毁。

4.信号量同步实战示例

#include <pthread.h> #include <semaphore.h> #include <stdio.h> #include <unistd.h> sem_t sem_H, sem_W; // 线程1:输出Hello void *th1(void *arg) { int i = 10; while (i--) { sem_wait(&sem_H); printf("hello "); fflush(stdout); sem_post(&sem_W); } return NULL; } // 线程2:输出World void *th2(void *arg) { int i = 10; while (i--) { sem_wait(&sem_W); printf("world\n"); sleep(1); sem_post(&sem_H); } return NULL; } int main(int argc, char **argv) { pthread_t tid1, tid2; // 初始化信号量:sem_H=1(线程1可以直接执行),sem_W=0(线程2等待) sem_init(&sem_H, 0, 1); sem_init(&sem_W, 0, 0); pthread_create(&tid1, NULL, th1, NULL); pthread_create(&tid2, NULL, th2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); sem_destroy(&sem_H); sem_destroy(&sem_W); return 0; }

四、死锁

由于锁资源安排的不合理(锁资源的申请和释放逻辑不对),导致进程、线程无法正常继续执行(推进)的现象。

产生死锁的四个必要条件:

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

破坏任意一个就能避免死锁(比如按固定顺序申请资源)。

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

社团招新海报制作:核心要素与设计逻辑

社团招新海报是社团与新生建立连接的第一媒介&#xff0c;其设计质量直接影响新生对社团的初始认知与参与意愿。不同于商业海报的品牌曝光诉求&#xff0c;社团招新海报需在短时间内完成“吸引注意力—传递核心价值—引导行动”的闭环&#xff0c;因此需围绕“目标定位、视觉层…

作者头像 李华
网站建设 2026/4/21 1:08:39

H3C防火墙Web登录实验

H3C防火墙Web登录实验 文章目录H3C防火墙Web登录实验一、背景二、实验拓扑图三、实验环境规划四、实验需求五、实验步骤第一步&#xff1a;在本机PC上创建微软环回适配器第二步&#xff1a;修改本机环回适配器的IP地址第三步&#xff1a;配置FW1&#xff0c;给FW1设置IP地址第四…

作者头像 李华
网站建设 2026/4/20 20:49:12

告别 “告警风暴” 与被动救火!8 大数据库监控维度提前规避 80% 故障

在数字化转型纵深推进的今天&#xff0c;数据库已成为支撑业务运转的核心基础设施。无论是电商平台的订单交易、金融机构的账务处理&#xff0c;还是政务系统的数据存储&#xff0c;数据库的稳定性与性能直接决定业务连续性和用户体验。权威数据显示&#xff0c;75%的严重业务中…

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

数据库监控实践中的六大常见误区与规避思路

在数据库运维的“江湖”里&#xff0c;监控系统不仅是DBA的眼睛&#xff0c;更是“保命”的护身符。然而&#xff0c;现实中有太多团队在建设数据库监控体系时&#xff0c;往往陷入了一些思维陷阱——钱花了&#xff0c;工具上了&#xff0c;故障发生时却依然手忙脚乱。真正成熟…

作者头像 李华
网站建设 2026/4/20 6:44:09

【c++进阶】在c++11之前的编译器的努力

关注我&#xff0c;学习c不迷路: 个人主页&#xff1a;爱装代码的小瓶子 专栏如下&#xff1a; c学习Linux学习 后续会更新更多有趣的小知识&#xff0c;关注我带你遨游知识世界 期待你的关注。 文章目录 第一章&#xff1a;C11前夜 - 编译器的"军阀混战"时代1.1…

作者头像 李华