当前位置: 首页 > news >正文

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. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

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

http://www.cnnetsun.cn/news/114819.html

相关文章:

  • DVWA -SQL Injection-通关教程-完结
  • AI大模型:未来就业的吞噬者还是创造者?揭秘其对普通人工作的影响!
  • 0x3f第七天 二叉搜索树
  • 扩容U盘,资料毁灭盘
  • 数据结构学习篇(5)---顺序表和链表的区别
  • 基于Vue.js和Spring Boot的新能源汽车充电站管理系统的设计与实现文献综述
  • 【Matlab】代码库:RGB三通道图像←互转→RGB次序平铺二维
  • 使用 html2canvas + jsPDF 生成PDF 的简单示例(含文字下沉修复)
  • Vue3+Monaco Editor封装及SQL编辑器实现
  • MiniCPM-V 4.5
  • Flutter工程化与协作实践指南
  • Excel技巧:提取身份证号码中的出生年月日
  • 软工毕业设计创新的开题分享
  • Oracle数据库物理备份与恢复实战指南
  • 告别“养死”魔咒!AI+知识库+物联网,打造零失败智能种植系统(附架构图+实操指南)
  • 安卓基础之《(4)—Activity组件(2)》
  • 打破数据堵点:6 大主流CRM厂商全链路数据流转能力横评与选型指南
  • 小程序毕设项目:基于springboot+微信小程序的校园活动管理系统设计与实现(源码+文档,讲解、 调试运行,定制等)
  • 小程序毕设项目:基于springboot+微信小程序的DIY电脑推荐与交流平台(源码+文档,讲解、 调试运行,定制等)
  • 小程序毕设项目:基于springboot+微信小程序的在线复习小程序(源码+文档,讲解、 调试运行,定制等)
  • 安徽做SCARA机器人的公司有哪些?
  • 【JavaWeb】MVC模式_理论简介
  • 【JavaWeb】日程管理01——登录页及数据校验功能
  • springboot中File默认路径
  • 【2025年AI 编程时代的热点】
  • 【C++ 笔记】从 C 到 C++:核心过渡 (中)
  • SQL约束解析
  • 地铁调研12-17
  • 现代软件测试工具全景对比与选型指南
  • 基于 Apache POI 的体检报告 Word 生成实战文档