news 2026/4/25 11:39:11

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/18 3:24:51

消息队列从入门到跑路,保姆级教程!傻子可懂

你是小阿巴&#xff0c;刚刚为电商系统的双 11 大促开发了秒杀抢购功能。 0 点秒杀开始&#xff0c;每秒上万个用户同时点击抢购按钮&#xff0c;你的数据库瞬间被打垮&#xff01; 你急得满头大汗&#xff0c;只能找到 “后端之狗” 鱼皮求助&#xff1a;阿巴阿巴…… 鱼皮看…

作者头像 李华
网站建设 2026/4/24 22:46:21

K8s -蓝绿发布与金丝雀发布

一、蓝绿发布&#xff1a;零停机切换与快速回滚核心原理蓝绿发布通过维护两个完全独立的生产环境&#xff08;“蓝” 和 “绿”&#xff09;实现无感知升级&#xff1a;蓝环境&#xff1a;当前运行的旧版本&#xff0c;处理全部用户流量。绿环境&#xff1a;部署新版本&#xf…

作者头像 李华
网站建设 2026/4/23 13:29:20

数据结构入门:二叉排序树的构建与相关算法

有序二叉树&#xff08;二叉排序树&#xff09;是数据结构中极具实用性的树形结构&#xff0c;其「左小右大」的核心特性让插入、查找、遍历操作具备高效性。一、二叉排序树的定义二叉排序树的核心规则&#xff1a;任意节点的左子树中&#xff0c;所有节点值 小于 该节点值&…

作者头像 李华
网站建设 2026/4/23 8:26:48

【HarmonyOS】个性化应用图标动态切换详解

在移动应用高度同质化的今天&#xff0c;图标早已不只是一个“入口”&#xff0c;而是用户对应用的第一印象。 在 HarmonyOS 生态中&#xff0c;系统能力不断下沉到应用层&#xff0c;开发者终于可以不再满足于“一个图标用到卸载”&#xff0c;而是让应用图标根据状态、主题或…

作者头像 李华
网站建设 2026/4/24 8:07:40

新手学编程:C、Java、Python 场景对比与发展ys#12篇

SQLAlchemy是Python中最流行的ORM&#xff08;对象关系映射&#xff09;框架之一&#xff0c;它提供了高效且灵活的数据库操作方式。本文将介绍如何使用SQLAlchemy ORM进行数据库操作。目录 安装SQLAlchemy核心概念连接数据库定义数据模型创建数据库表基本CRUD操作查询数据关系…

作者头像 李华