【Linux】线程安全与线程同步

【Linux】线程安全与线程同步
在这里插入图片描述

个人主页~


线程安全与线程同步

一、线程安全

1、概念

我们这里通过理解重入与线程安全的关系来理解线程安全

线程安全多个线程并发同一段代码时,不会出现不同的结果
重入
同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,一个函数在重入的情况下运行结果不会出现任何问题,这样的函数称为可重入函数,否则,就是不可重入函数

2、常见线程情况

  • 常见线程不安全情况
    • 不保护共享变量的函数
    • 函数状态随着被调用,状态发生变化的函数
    • 返回指向静态变量指针的函数
    • 调用线程不安全函数的函数
  • 常见线程安全情况
    • 每个线程对全局变量或静态变量只有读权限没有写权限
    • 类或接口对于线程来说是原子操作
    • 多个线程之间的切换不会导致该接口的执行结果存在二义性

3、常见重入情况

  • 常见不可重入情况
    • 调用了malloc或new函数,因为malloc函数是用全局链表来管理堆的
    • 调用了标准IO库函数,标准IO库中很多实现都以不可重入的方式使用全局数据结构
    • 可重入函数体内使用了静态的数据结构
  • 常见可重入情况
    • 不使用全局和静态变量
    • 不使用malloc或new出来的空间
    • 不调用不可重入函数
    • 不返回静态或全局数据,所有数据都由函数的调用者提供
    • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

4、可重入与线程安全

  • 联系
    • 函数可重入就代表着线程安全
    • 函数不可重入,那就不能由多个线程使用,有可能引发线程安全问题
    • 如果一个函数中有全局变量,这么这个函数既是不可重入的又不是线程安全的
  • 区别
    • 可重入函数是线程安全函数的一种
    • 线程安全不一定是可重入的,但可重入的一定是线程安全的
    • 如果将对临界资源的访问加上锁,那么这个函数是线程安全的,但如果这个重入函数的锁还没释放则会产生死锁,因此是不可重入的

5、死锁

(一)概念

死锁是指在一组进程或线程中的各个进程或线程均占有不会释放的资源,但因互相申请被其他进程或线程所占用的不会释放的资源而处于的一种永久等待的状态

死锁都是人为产生的,我们可以规避掉的

(二)死锁的四个必要条件

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

(三)避免死锁的方法

  • 当有死锁的时候,必然是满足上面这四个条件的,但满足上面四个条件不一定形成死锁,我们只要破坏上面其中任何一条条件就可以避免死锁
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

二、线程同步

1、概念

在纯互斥的场景下,由于我们的锁只有少量个,多个线程同时竞争锁,但是得到锁的只有一小部分线程,剩下的线程就会因为等待,产生 “线程饥饿” 问题,线程饥饿本质上就是抢夺不到锁的线程,即抢夺不到资源的线程在等待锁的释放,为了避免这里的饥饿的问题,我们就通过线程同步来在保证数据安全的前提下,让线程按照顺序访问临界资源

2、条件变量

(一)概念

当一个线程互斥的访问某个变量时,它可能在其他线程改变状态之前什么也做不了,比如一个线程访问队列时,发现队列为空,那么它只能等待,直到其他进程将一个节点添加到队列当中,这个时候我们就可以利用条件变量来规避这种情况

(二)调用函数

(1)初始化条件变量
#include<pthread.h>intpthread_cond_init(pthread_cond_t*restrict cond,constpthread_condattr_t*restrict attr);

返回值:成功返回0,失败返回非0错误码
cond:指向要初始化的条件变量的指针,pthread_cond_t 是一个表示条件变量的数据类型
attr:指向条件变量属性对象的指针,传入NULL表示使用默认属性

(2)销毁条件变量
#include<pthread.h>intpthread_cond_destroy(pthread_cond_t*cond);

返回值:成功返回0,失败返回非0错误码
cond:指向要销毁的条件变量的指针

(3)等待条件被满足
#include<pthread.h>intpthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutex_t*restrict mutex);

返回值:成功返回0,失败返回非0错误码
cond:指向要操作的条件变量的指针,条件变量用于线程之间的等待和通知机制
mutex:指向互斥锁的指针,互斥锁用于保护共享资源,确保线程安全

调用该函数时,线程会自动释放互斥锁mutex,以便其他线程可以获取锁,当收到信号被唤醒后,线程会重新尝试获取互斥锁

(4)唤醒等待线程
#include<pthread.h>//唤醒一个等待线程intpthread_cond_signal(pthread_cond_t*cond);//唤起所有等待线程intpthread_cond_broadcast(pthread_cond_t*cond);

返回值:成功返回0,失败返回非0错误码
cond:指向要操作的条件变量的指针,条件变量是一种用于线程同步的机制,允许线程在某个条件不满足时阻塞,直到其他线程通知该条件已经满足

如果一个线程执行 pthread_cond_broadcast,它会将所有等待该条件变量的线程全部唤醒,若执行 pthread_cond_signal,则只会唤醒至少一个等待该条件变量的线程,而非只唤醒当前线程

(三)样例

#include<iostream>#include<pthread.h>#include<vector>#include<unistd.h>usingnamespace std;#defineNUM4int cnt =0;//条件变量函数的用法几乎与锁函数的用法完全等同//定义全局锁和全局条件变量 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void*Count(void*args){pthread_detach(pthread_self());// 线程分离,跑完就不管了,不在乎它的返回值// Linux是64位机,指针是8字节,uint是unsigned long long int uint64_t num =(uint64_t)args; cout <<"Thread "<< num <<" is creat success"<< endl;usleep(100000);while(true){pthread_mutex_lock(&lock);//这里pthread_cond_wait要在临界区的原因是://因为 pthread_cond_wait 是让线程去等待,等待的原因一定是临界资源不就绪//而临界资源是否就绪,是通过判断得来的,判断也是访问临界资源,所以判断必须在加锁之后pthread_cond_wait(&cond,&lock);//线程在此处进入等待状态,等待条件变量 cond 发出信号 cout <<"Thread "<< num <<" is running... cnt: "<< cnt << endl; cnt++;usleep(10000);pthread_mutex_unlock(&lock);}}intmain(){for(uint64_t i =1; i <= NUM; i++){ pthread_t tid;//这里的第四个参数,如果想要与新线程共享这个参数的话,可以设为(void*)&i,进行传址调用//我们这里要传值调用,不能让它用ipthread_create(&tid,nullptr, Count,(void*)i);usleep(1000);}//指定唤醒线程来访问临界资源while(true){sleep(1);pthread_cond_signal(&cond);// 唤醒一个线程 cout <<"signal one thread..."<< endl;}return0;}
在这里插入图片描述

今日分享就到这了~

在这里插入图片描述

Read more

一文读懂Ingress-Nginx以及实践攻略

一文读懂Ingress-Nginx以及实践攻略

一文读懂Ingress-Nginx以及实践攻略 目录 * 1 概念 * 1.1 什么是Ingress? * 1.1.1 主要功能: * 1.2 Ingress的组件 * 1.3 什么是ingress-nginx * 1.4 ingress-nginx优点和限制 * 1.5 版本兼容性矩阵 * 2 实践: Ingress nginx部署 * 2.1 使用helm部署ingress-nginx * 2.1.1 安装和配置Helm * 2.1.2 配置和创建Ingress-Nginx * 2.2 使用yaml文件部署ingress-nginx * 2.3 部署后查看ingress状态 * 2.4 创建实例测试 Ingress * 2.4.

By Ne0inhk

【Openclaw】unauthorized: gateway token mismatch (open the dashboard URL and paste the token in Co

unauthorized: gateway token mismatch (open the dashboard URL and paste the token in Control UI settings) 故障现象: 使用Windows下的浏览器打开Openclaw的聊天界面(之前是可以正常使用的),结果报错:  故障原因: 可能是服务器Ubantu里面的Openclaw出了问题。 解决办法: 在乌班图Ubantu的terminal里面输入这两个命令: Ubantu里面的Firfox浏览器就可以正常访问了。 http://127.0.0.1:18789/config Thanks to : 1). Kimi OpenClaw 是一个开源的《猫和老鼠》游戏克隆项目。要重新启动它,你需要先停止正在运行的进程,然后重新启动。 以下是常用的 Linux 命令: 查找并终止现有进程 ```bash # 查找 OpenClaw

By Ne0inhk
Oracle迁移至金仓数据库:PL/SQL匿名块执行失败的深度排查指南

Oracle迁移至金仓数据库:PL/SQL匿名块执行失败的深度排查指南

摘要:本文系统探讨了Oracle数据库迁移至金仓数据库(KES)过程中PL/SQL匿名块执行失败的排查方法。重点分析了数据类型不兼容(字符串、数值、日期)、系统函数适配、动态SQL处理、异常机制重构等核心问题,并提供了性能优化策略与迁移验证方案。文章强调迁移不仅是语法转换,更要确保语义对等,建议建立分类框架系统化排查错误。通过典型场景示例展示了空字符串处理、数值精度控制等具体解决方案,为数据库迁移项目提供了实用指南。 前言:迁移浪潮中的关键挑战 在当前数字化转型与信息技术应用创新发展的双重驱动下,众多企业正面临着从Oracle数据库向国产数据库平台迁移的重要任务。在这一过程中,电科金仓数据库(KingbaseES,简称KES)凭借其卓越的Oracle兼容性、高性能和可靠性,成为许多关键业务系统迁移的首选目标。然而,迁移并非简单的数据搬运,特别是业务逻辑核心的PL/SQL代码迁移,常常成为项目推进的难点所在。 PL/SQL匿名块作为存储过程、函数等程序单元的基础构建块,在企业应用中广泛存在。这些匿名块往往封装了复杂的业务逻辑,从简单的数据校验到多步骤的事务处理,其执行稳定性直接关

By Ne0inhk
(第四篇)Spring AI 核心技术攻坚:多轮对话与记忆机制,打造有上下文的 AI

(第四篇)Spring AI 核心技术攻坚:多轮对话与记忆机制,打造有上下文的 AI

摘要         在大模型应用开发中,“上下文丢失” 是多轮对话场景的核心痛点,直接导致 AI 回复割裂、用户体验拉胯。本文基于 Spring AI 生态,从对话记忆的本质出发,深度拆解短期 / 长期 / 摘要三类记忆的设计逻辑,对比 Redis 缓存与数据库持久化的技术选型方案,详解上下文压缩的关键技巧,并通过完整实战案例,手把手教你构建支持 100 轮对话的高可用智能客服。全程贯穿 “从内存存储到分布式记忆” 的进阶思路,既有底层原理剖析,又有可直接落地的代码实现,帮你彻底掌握 Spring AI 记忆机制的核心玩法。 引言         用过 Spring AI 开发对话应用的同学都懂:默认情况下 LLM 是 “鱼的记忆”—— 每次请求都是独立会话,无法记住上一轮的对话内容。比如智能客服场景中,用户先说明 “我要查询订单物流”,再提供 “订单号 12345”

By Ne0inhk