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

【Linux】Linux 操作系统 - 33 , 线程(二) 线程互斥和同步 , 带你对线程使用深刻理解 !

文章目录

  • 需要知道的知识点
  • 线程互斥
  • 一 、是什么 ?
  • 二 、什么叫做数据竞争 , 数据不一致 ?
  • 三 、互斥锁(互斥量) (掌握)
    • 1 . 介绍
    • 2 . 锁相关接口
      • 2.1 创建锁和销毁锁
      • 2.2 解锁和解锁
      • 2.3 互斥锁正确使用步骤
    • 3 . 互斥锁的基本使用
      • 3.1 全局锁的使用
      • 3.2 局部锁的使用
    • 4 . 互斥锁的原理 (掌握)
      • 总结好了 (面试)
  • 线程同步
  • 一 、线程同步是什么 ?
  • 二 、条件变量
    • 1 . 是什么 ?
    • 2 . 使用 - 相关接口
      • 2.1 创建和销毁
      • 2.2 等待和唤醒
      • 2.3 使用步骤
    • 3 . 条件变量使用注意事项(特别重要 , 要注意 !!!!!)
      • 面试回答 : 为什么条件变量必须使用 while 循环 ?
      • 面试回答 : 了解 pthread_cond_wait 吗 ? 为什么参数要用锁 ??
    • 4 . 条件变量的使用
  • 三 、POSIX信号量
    • 1 . 是什么 ?
    • 2 . 理解信号量
    • 3 . 多线程使用资源分类 (重要)
    • 4 . 信号量的相关接口
      • 4.1 创建和销毁
      • 4.2 等待(P操作)和唤醒(V操作)
    • 5 . 信号量的基本使用
  • 线程互斥和同步的区别 , 条件变量和信号量的区别 (面试可能考)
  • 思维导图总结


需要知道的知识点

在这里插入图片描述


线程互斥

一 、是什么 ?


   线程互斥(Thread Mutual Exclusion)是多线程编程中的一个重要概念,用于解决多个线程同时访问共享资源时可能引发的数据竞争(Data Race)和不一致性问题的一种解决方案 。


二 、什么叫做数据竞争 , 数据不一致 ?


  在线程中 , 我们可能创建多个线程 , 但是多个线程访问了共同资源 , 多个线程就是多个执行流 , 但资源只有一份 , 所以多个线程就要竞争 , 在竞争过程中 , 可能就会引发数据不一致问题 ! 以下例子来具体理解 :

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
//////// 演示什么是数据不一致 !int gticksts = 1000; // 总票数// 这个是抢票 , 票数为 1000 , 多个线程来执行
void *Tickets(void *args)
{std::string name = static_cast<const char *>(args);while (1){if (gticksts > 0){usleep(1000); // 1000 ms std::cout << name << "sells ticket : " << gticksts << std::endl;gticksts--;}else{break;}}return nullptr;
}int main()
{std::vector<pthread_t> threads;// 创建5个线程for (int i = 1; i <= 5; ++i){pthread_t tid;char *name = new char[64]; // 开辟一个空间snprintf(name, 64, "thread-%d", i);pthread_create(&tid, nullptr, Tickets, name);threads.emplace_back(tid);}//等待 5 个线程 for(auto& tid : threads){pthread_join(tid , nullptr);}return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 理解

      也就是说 : 当前我们的代码是允许多个执行流并行执行的 , 即 : 你在执行的时候 , 我也在执行 , 执行不是原子的 .

在这里插入图片描述

在这里插入图片描述

  所以 , 对于共享资源 , 我们要对共享资源进行保护 , 这样就会解决数据不一致的问题 , 本质就是 : 只允许一个执行流来执行就行了 , 其它执行流不要影响我 !


三 、互斥锁(互斥量) (掌握)


  这个就是解决数据不一致问题的一个重要的方法 !

1 . 介绍

在这里插入图片描述


2 . 锁相关接口

2.1 创建锁和销毁锁

在这里插入图片描述

2.2 解锁和解锁

在这里插入图片描述

2.3 互斥锁正确使用步骤

在这里插入图片描述


3 . 互斥锁的基本使用


  用互斥锁解决一下 , 以上抢票数据不一致的问题 !

3.1 全局锁的使用

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
//////// 演示什么是数据不一致 !int gticksts = 1000; // 总票数// 1. 定义一个全局的锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // INITIALIZER - initiallizer 初始化// 这个是抢票 , 票数为 1000 , 多个线程来执行
void *Tickets(void *args)
{std::string name = static_cast<const char *>(args);while (1){// 这里是临界区的开始 , 因为里面访问了共享资源// 这里加锁pthread_mutex_lock(&mutex);if (gticksts > 0){usleep(1000); // 1000 msstd::cout << name << " sells ticket : " << gticksts << std::endl;gticksts--;// 这里解锁pthread_mutex_unlock(&mutex);} // 到这里临界区的结束 ..else{// 这里必须解锁 !!!!!!!!!!!!!  防止出现死锁 , 当票数 = 0 时会走这里pthread_mutex_unlock(&mutex);break;}}return nullptr;
}int main()
{std::vector<pthread_t> threads;// 创建5个线程for (int i = 1; i <= 5; ++i){pthread_t tid;char *name = new char[64]; // 开辟一个空间snprintf(name, 64, "thread-%d", i);int n = pthread_create(&tid, nullptr, Tickets, name);if (n == 0){std::cout << "Create " << name << " Success ! " << std::endl;threads.emplace_back(tid);}}// 等待 5 个线程for (auto &tid : threads){int n = pthread_join(tid, nullptr);if (n == 0){std::cout << " Join Success ! " << std::endl;}}return 0;
}

在这里插入图片描述


3.2 局部锁的使用

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
//////// 演示什么是数据不一致 !int gticksts = 1000; // 总票数// 2. 定义了一个局部锁
pthread_mutex_t mutex;// 这个是抢票 , 票数为 1000 , 多个线程来执行
void *Tickets(void *args)
{std::string name = static_cast<const char *>(args);while (1){// 这里是临界区的开始 , 因为里面访问了共享资源// 这里加锁pthread_mutex_lock(&mutex);if (gticksts > 0){usleep(1000); // 1000 msstd::cout << name << " sells ticket : " << gticksts << std::endl;gticksts--;// 这里解锁pthread_mutex_unlock(&mutex);} // 到这里临界区的结束 ..else{// 这里必须解锁 !!!!!!!!!!!!!  防止出现死锁 , 当票数 = 0 时会走这里pthread_mutex_unlock(&mutex);break;}}return nullptr;
}int main()
{std::vector<pthread_t> threads;// 局部锁初始化pthread_mutex_init(&mutex, nullptr);// 创建5个线程for (int i = 1; i <= 5; ++i){pthread_t tid;char *name = new char[64]; // 开辟一个空间snprintf(name, 64, "thread-%d", i);int n = pthread_create(&tid, nullptr, Tickets, name);if (n == 0){std::cout << "Create " << name << " Success ! " << std::endl;threads.emplace_back(tid);}}// 等待 5 个线程for (auto &tid : threads){int n = pthread_join(tid, nullptr);if (n == 0){std::cout << " Join Success ! " << std::endl;}}// 2. 定义局部锁时 :// 这里销毁锁pthread_mutex_destroy(&mutex);return 0;
}

在这里插入图片描述


4 . 互斥锁的原理 (掌握)

在这里插入图片描述

总结好了 (面试)

在这里插入图片描述


线程同步


  考虑一个问题 , 单纯的线程互斥有问题吗 ? 线程互斥是解决数据不一致问题的 , 但是对多个线程来说有什么缺点 ??

在这里插入图片描述
  所以 , 单纯的互斥是有缺陷的 , 为了解决这样的缺陷 , 引出了同步的话题 !

一 、线程同步是什么 ?


  在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步 。

  • 什么叫做饥饿呢 ??

      以上只有互斥的例子 , 在多个线程运行时 , 只有一些线程能够得到锁 , 访问临界资源 ,但始终有一些线程没有机会得到资源 , 这样的线程叫 : 饥饿线程 。这是不公平的 .

  然而 , 同步就是让所有线程的访问有序的进行 , 均可访问临界资源 , 谁排在前面谁先访问 , 所以可以有效的解决饥饿问题 。


二 、条件变量


  条件变量是实现同步的一种方式 !

1 . 是什么 ?

  • 互斥时 , 一个线程竞争锁成功 , 该线程访问临界区 , 进行相关操作 , 但是其余线程就要一直阻塞等待直到锁的释放 , 什么事情都做不了 .
  • 条件变量 : 特点就是允许竞争到锁的线程唤醒其它等待的线程 .

概念 :

  条件变量是一种线程同步机制,用于在线程间传递 “某个条件是否满足” 的信号。它允许线程在条件不满足时主动暂停执行(释放锁并进入等待状态),直到其他线程修改条件并显式唤醒它。条件变量中是有等待队列的 .


2 . 使用 - 相关接口

2.1 创建和销毁

在这里插入图片描述

2.2 等待和唤醒


  该接口就能体现出条件变量的特性了 !

在这里插入图片描述
在这里插入图片描述

2.3 使用步骤

在这里插入图片描述


3 . 条件变量使用注意事项(特别重要 , 要注意 !!!)


//////////// 线程等待
// 错误示例:使用if检查条件可能导致程序崩溃
pthread_mutex_lock(&mutex);
if (buffer_empty()) {  // 检查缓冲区是否为空pthread_cond_wait(&cond, &mutex);  // 若为空则等待
}
// 风险点:若被虚假唤醒或条件已变化,会直接执行后续代码
consume_data();  // 可能访问空缓冲区,导致程序崩溃
pthread_mutex_unlock(&mutex);//////////// 线程唤醒
...... 这里唤醒该条件变量下的一个线程pthread_cond_signal(&cond);
  • 为什么必须使用 while 循环 ? (详细解释)

在这里插入图片描述

面试回答 : 为什么条件变量必须使用 while 循环 ?

在这里插入图片描述

面试回答 : 了解 pthread_cond_wait 吗 ? 为什么参数要用锁 ??

在这里插入图片描述


4 . 条件变量的使用

// 条件变量的使用练习
// 条件变量是实现同步机制的 -- 让多线程有顺序的访问
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <cstdio>// 全局计数器
int counter = 0;       // 全局计数器
#define TARGET_COUNT 5 // 目标计数
// 全局锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//////////////////// 1. 全局的条件变量 ////////////////////
// pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//////////////////// 2. 局部的条件变量 ////////////////////pthread_cond_t cond;// 工作线程:增加计数器
void *worker(void *args)
{std::string name = static_cast<char *>(args);for (int i = 0; i < TARGET_COUNT; i++){pthread_mutex_lock(&mutex);// 增加计数器counter++;std::cout << "工作线程 : " << name << " - > 计数器增加到 : " << counter << std::endl;// 当计数器达到目标值时,通知等待的线程if (counter == TARGET_COUNT){// 唤醒该条件变量下的一个线程pthread_cond_signal(&cond);std::cout << " 工作线程: 已发出通知,计数器达到目标值 !" << std::endl;}pthread_mutex_unlock(&mutex);// 短暂休眠,模拟工作耗时usleep(100000);}return nullptr;
}// 主线程
int main()
{//////////////////// 2. 局部的条件变量 ////////////////////pthread_cond_init(&cond, nullptr);std::vector<pthread_t> threads;// 创建5个线程for (int i = 1; i <= 5; ++i){pthread_t tid;char *name = new char[64]; // 开辟一个空间snprintf(name, 64, "thread-%d", i);int n = pthread_create(&tid, nullptr, worker, name);if (n == 0){threads.emplace_back(tid);}}// 等待 5 个线程for (auto &tid : threads){int n = pthread_join(tid, nullptr);if (n == 0){std::cout << " Join Success ! " << std::endl;}}// 主线程等待计数器达到目标值pthread_mutex_lock(&mutex);while (counter < TARGET_COUNT){printf("主线程: 等待计数器达到 %d...\n", TARGET_COUNT);pthread_cond_wait(&cond, &mutex);printf("主线程: 被唤醒,当前计数器值为 %d\n", counter);}pthread_mutex_unlock(&mutex);pthread_cond_destroy(&cond);return 0;
}

在这里插入图片描述


三 、POSIX信号量


  信号量是实现同步的一种方式 !

1 . 是什么 ?


  信号量是实现同步机制的一种方式,它通过一个受保护的整数变量及相关的等待队列,结合 P 操作(等待,将信号量值减 1,若值为负则阻塞调用者)和 V 操作(信号,将信号量值加 1,若有阻塞线程则唤醒其一),实现对共享资源的有序访问控制。

信号量的两个核心操作 :

  • P 操作 – 等待资源

      该操作是对信号量的值 -- , 若结果为负,调用者阻塞等待 。该操作必须是原子的 , 在执行过程中不允许被其它线程打断 。

  • V 操作 – 唤醒资源

      该操作是对信号量的值 ++ , 若有阻塞线程唤醒其中一个。该操作必须是原子的 , 在执行过程中不允许被其它线程打断 。


2 . 理解信号量

在这里插入图片描述


3 . 多线程使用资源分类 (重要)

在这里插入图片描述
在这里插入图片描述


4 . 信号量的相关接口


  信号量也是 pthread 库封装的 , 所以编译时要链接该库 !

4.1 创建和销毁

在这里插入图片描述

4.2 等待(P操作)和唤醒(V操作)

在这里插入图片描述


5 . 信号量的基本使用

// 信号量接口的使用#include <iostream>
#include <pthread.h>
#include <string>
#include <semaphore.h>
#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
sem_t sem; // 信号量 , 主新线程共享void *work(void *args)
{// 为主线程创建信号量 ,std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){// 消耗资源 -- P操作 , --信号量sem_wait(&sem);pthread_mutex_lock(&mutex);std::cout << "我是新线程 : " << name << " 我消耗了一个资源 !" << std::endl;pthread_mutex_unlock(&mutex);sleep(2); // 我每隔2s拿一个}return nullptr;
}int main()
{// 考虑一个问题 , 是先创建线程呢 ?? 还是先创建信号量呢 ??// 先创建信号量 , 因为信号量是表示资源的多少 , 表示有几个线程访问资源// 所以先创建信号量 , 表示有线程就就绪了 , 如果信号量都创建失败了 , 线程就没有必要创建了// 初始化信号量 , 刚开始没有资源 , 所以为 : 0sem_init(&sem, 0, 0);// 创建一个线程pthread_t tid;pthread_create(&tid, nullptr, work, (void *)"thread-1");// 主线程// 主线程放资源 , P操作int cnt = 5;while (cnt--){sleep(1); // 每隔 1s 放一个资源// V操作 , ++ 信号量 , 所以放资源sem_post(&sem);pthread_mutex_lock(&mutex);std::cout << " 我是主线程 , 我放了一个资源 , 等待新线程消耗 .. " << std::endl;pthread_mutex_unlock(&mutex);}// 等待线程pthread_join(tid, nullptr);sem_destroy(&sem);return 0;
}

线程互斥和同步的区别 , 条件变量和信号量的区别 (面试可能考)

总结好了 :
在这里插入图片描述

思维导图总结

在这里插入图片描述

http://www.dtcms.com/a/315030.html

相关文章:

  • 《Python 实用项目与工具制作指南》· 2.2 变量
  • JVM调优工具详解
  • 把“距离过近”的节点(或端点)合并成一个,避免重复。机器学习 python
  • web:ts元组
  • 【RH124知识点问答题】第8章 监控和管理 Linux 进程
  • Bean的生命周期和循环依赖问题的解决
  • 防火墙认证用户部署
  • 开发规范(一)移动端
  • 多线程(一)
  • 【C#】操作Execl和Word文件-1
  • 子词分词器(Byte Pair Encoding + WordPiece)
  • 如何给Word和WPS文档添加密码或取消密码
  • 【Java】使用FreeMarker来实现Word自定义导出
  • 反转字符串中的元音字母:Swift 双指针一步到位
  • EXPLAIN工具:查询执行计划分析与索引诊断
  • 【数据结构】排序(sort) -- 插入排序
  • 如何设置端口映射?防火墙/路由器/纯软件工具多种常用方案步骤,确保任意内网ip端口映射公网访问到
  • 《汇编语言:基于X86处理器》第11章 复习题和练习
  • RocketMQ与Kafka 消费者组的‌重平衡操作消息顺序性对比
  • Hadoop MapReduce 3.3.4 讲解~
  • Linux系统编程-文件操作(黑马笔记)
  • 基于Springboot+Mybatis+thymeleaf的个人博客系统的设计与实现
  • EXCEL删除数据透视表
  • 洛谷 P3373 【模板】线段树 2- 普及+/提高
  • C# 类型
  • 基于PSO-NSGAIII混合优化的生产调度算法matlab仿真,输出甘特图,对比PSO和NSGAIII
  • traefik网关鉴权中间件转发multipart/form-data请求的multipart: NextPart: EOF问题
  • 09 Linux基础(8.4)
  • (一)vue3项目初始化(create-vue)
  • 构建属于自己的第一个 MCP 服务器:初学者教程