项目并发遇到相关问题

Posted xiaowan2333

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了项目并发遇到相关问题相关的知识,希望对你有一定的参考价值。

在项目中,常见的并发问题包括以下几种情况:

1. 竞态条件(Race Condition):多个线程或进程同时访问和修改共享资源,导致结果的不确定性。处理竞态条件可以采用以下方法:

  • 使用互斥锁(Mutex)或信号量(Semaphore)来保护共享资源,确保同一时间只有一个线程能够访问资源。
  • 使用原子操作(Atomic Operation)来保证对共享资源的读写操作的原子性。
  • 使用线程安全的数据结构,如线程安全的集合类,来避免手动处理锁和同步。

2. 死锁(Deadlock):多个进程或线程相互等待对方持有的资源,导致系统无法继续执行。处理死锁可以采用以下方法:

  • 避免循环等待:对资源的获取顺序进行统一,避免循环依赖。
  • 使用资源分级:确保每个线程只请求当前级别的资源,避免跨级资源的请求。
  • 引入超时机制:在获取资源时设置超时时间,超时后放弃请求并释放已占有的资源。

3. 数据一致性问题:多个线程或进程同时读写共享数据,导致数据的不一致性。处理数据一致性问题可以采用以下方法:

  • 使用synchronized或Lock等同步锁
  • 使用事务(Transaction)来保证对共享数据的原子性操作。事务可以提供隔离级别,确保数据的一致性。
  • 使用乐观锁(Optimistic Locking)或悲观锁(Pessimistic Locking)来控制对共享数据的访问,避免并发冲突。
  • 使用基于分布式锁的方式来实现并发控制,例如使用 Redis、ZooKeeper 等分布式锁工具。

4. 资源限制:并发操作过多导致系统资源(如线程、内存、连接池等)耗尽。处理资源限制问题可以采用以下方法:

  • 调整系统配置:增加可用资源的数量或提高资源的限制。
  • 实现资源池:对一些资源进行池化管理,复用资源对象,避免频繁创建和销毁。

5. 缓存一致性:在分布式系统中,多个节点同时对缓存进行更新,导致缓存数据的不一致性。处理缓存一致性问题可以采用以下方法:

  • 使用分布式缓存方案,如Redis等,提供缓存的一致性保证。
  • 实现缓存失效策略,如过期时间、定期刷新等,保证缓存数据的及时更新。

除了以上情况,还可能存在其他特定的并发问题

具体处理方法应根据具体情况进行分析和选择。在开发过程中,合理的并发控制和调度
库相关操作的并发问题

我在项目中具体遇到的一个具体问题:

根据车牌号和时间创建订单时出现的并发,并发时时间可能不一致。需求是不能重复创建,使用车牌号和时间戳生成的唯一ID
开始设想是通过事务和数据库主键唯一约束实现接口幂等,使用synchronized实现方法同步避免两个事务不可见并发插入。
在实现测试时发现还是会出现并发问题,原因是因为spring的事务和synchronized冲突,事务运行是基于代理方法,锁不生效。
最终使用基于redis的分布式锁方案,在插入数据时先获取锁。

网络编程遇到相关问题

为什么出现粘包问题?

TCP协议就类似于打电话
只有在TCP协议中才会出现粘包现象,因为TCP协议是流式协议
它的特点是将数据量小并且时间间隔比较短的数据一次性打包发送出去
本质其实还是因为我们不知道需要接收的数据的长短

如何解决粘包问题?

1 发送数据直接先告诉对方数据量的大小
2 利用struct模块定制我们自己的消息传输协议

TCP的大文件上传

客户端:
        1.制作字典的报头(固定4个长度)
        2.发送报头
        3.发送字典
        4.最后再发真实数据
服务端:
        1.先接收4个长度的报头
        2.解析报头获取字典的长度
        3.接收字典数据  从字典中获取真实数据的详细信息
        4.接收真实数据

udp协议

UDP协议就类似于发短信
1.udp协议客户端允许发空
2.udp协议不会粘包
3.udp协议服务端不存在的情况下,客户端照样不会报错
4.udp协议支持并发

UDP叫数据报协议,意味着发消息都带有数据报头
udp的server不需要就行监听也不需要建立连接
在启动服务之后只能被动的等待客户端发送消息过来,客户端发送消息的时候,要带上服务端的地址
服务端在回复消息的时候,也需要带上客户端的地址

多道技术

解决cpu在执行程序,遇到io时,不干活的情况
时间上的复用(单个cpu的电脑上,起多个应用程序。cpu快速切换,给人的感觉是同时运行)
空间上的复用(多个程序共一套硬件设备,它是多道技术实现时间上的复用的基础,不然还要去硬盘读数据)
一个任务占用cpu时间过长或被操作系统强行剥夺走cpu的执行权限(比起串行效率反而降低)
一个任务执行过程中遇到io操作,也会被操作系统强行剥夺走cpu的执行权限(比起串行效率提高)

串行|并发

串行:一个程序完完整整的运行完毕,才能运行下一个程序
并发:看上去像同时运行

进程理论

程序:一堆代码
进程:正在运行的程序
进程是一个实体,每一个进程都有它自己独立的内存空间

同步和异步:针对任务的提交方式

同步:提交任务之后原地等待任务的返回结果,期间不做任何事!
异步:提交任务之后,不等待任务的返回结果,执行运行下一行代码!

阻塞与非阻塞:针对程序运行的状态

阻塞:遇到io操作   >>> 阻塞态
非阻塞:就绪或者运行态  >>> 就绪态,运行态

互斥锁:锁千万不要随意去用

会将并发变成串行,牺牲了效率但是保证了数据的安全
锁一定要在主进程中创建,给子进程去用
解决多个进程操作同一份数据,造成数据不安全的情况
加锁会将并发变成串行
锁通常用在对数据操作的部分,并不是对进程全程加锁
一把锁不能同时被多个人使用,没有抢到的人,就一直等待锁释放

生产者消费者模型

生产者:生产数据(做包子的)
消费者:处理数据(吃包子的)
两者之间的通信介质:队列/管道
供需不平衡:
    队列: 
        生产者生产的数据放到队列里面
        消费者去队列里面获取数据

IPC机制

进程间通信
    进程与进程之间是数据隔离的
    管道/队列(管道+锁)
    队列:先进先出
    堆栈:先进后出
    q.put()  放入值
    q.get()  获取队列里面的值(同一时刻只能有一个任务来队列中获取数据)
    两者在存放值和取值的时候都会出现阻塞的情况(队列满了,队列空了)

线程理论

把操作系统比喻成工厂
进程:资源单位(工厂里面的车间)
线程:执行单位(车间里面的流水线)
任何一个进程都自带一个"主"线程
进程中的线程数据是共享的,
开起进程的开销要远远大于开启线程的开销
开启进程:申请空间,拷贝代码都需要耗时
开启线程:开销极小,几乎在代码执行的同时线程就已经创建

线程之间数据共享

线程之间数据共享
多个线程操作同一份数据???出现数据不安全的情况
涉及到多个线程或进程操作同一份数据的时候,通常都需要将并行并发变成串行
虽然牺牲了效率但是提高了数据的安全性
针对不同的数据,需要加不同的锁
锁(独立卫生间)

GIL全局解释器锁

只在Cpython解释器中
由于Cpython内存管理不是线程安全的!
同一进程下的多个线程在同一时刻只能有一个线程被执行
必须先抢解释器才能被cpu执行
GIL是加在Cpython解释器上的一把锁,并不能保证数据的安全,想保证数据的安全,就必须加不同的锁

python内存管理

内存管理>>>垃圾回收机制
1.引用计数
2.标记清除
3.分代回收

死锁与递归锁

即便你记住了每acquire一次就release一次的操作,也会产生死锁现象
递归锁:可以连续的acquire(),每acquire()一次计数加一

信号量

如果把互斥锁比喻成独立卫生间,那么信号量就相当于多个卫生间,一个可以指定多个线程访问

event事件

event = Event()
        
event.set()  # 告诉另外一个子线程 你可以运行了
        
event.wait()  # 等待别人给我发set()信号

socket服务端实现并发

固定的ip和port
24小时提供服务
能够实现并发

进程池线程池

线程不可能无限制的开下去,总要消耗和占用资源

进程池线程池概念:硬件有极限,为了减轻硬件压力,所以有了池的概念
池:
    为了减缓计算机硬件的压力,避免计算机硬件设备崩溃
    虽然减轻了计算机硬件的压力,但是一定程度上降低了持续的效率
进程池线程池:
    为了限制开设的进程数和线程数,从而保证计算机硬件的安全

- concurrent.futures模块导入
- 线程池创建(线程数=cpu核数*5左右)
- submit提交任务(提交任务的两种方式)
- 异步提交的submit返回值对象
- shutdown关闭池并等待所有任务运行结束
- 对象获取任务返回值
- 进程池的使用,验证进程池在创建的时候里面固定有指定的进程数
- 异步提交回调函数的使用

协程,io密集型会提高效率

- 进程:资源单位
- 线程:执行单位
- 协程:单线程下实现并发(能够在多个任务之间切换和保存状态来节省IO),这里注意区分操作系统的切换+保存状态是针对多个线程而言,而我们现在是想在单个线程下自己手动实现操作系统的切换+保存状态的功能

协程这个概念完全是程序员自己想出来的东西,它对于操作系统来说根本不存在。操作系统只知道进程和线程。
注意:
    并不是单个线程下实现切换+保存状态就能提升效率,因为你可能是没有遇到io也切,那反而会降低效率

实现高并发的原理

将单个线程的效率提升到最高,多进程下开多线程,多线程下用协程>>> 实现高并发!!!

协程实现服务端客户端通信

链接和通信都是io密集型操作,我们只需要在这两者之间来回切换其实就能实现并发的效果
服务端监测链接和通信任务,客户端起多线程同时链接服务端

以上是关于项目并发遇到相关问题的主要内容,如果未能解决你的问题,请参考以下文章

压力测试报出503错误---ASP.NET支持大并发的相关配置

项目中遇到并发问题和解决办法

一个老项目的高并发改造,遇到的redis连接不释放问题。

线程异步并发处理邮件发送

MySQL并发相关的参数

线上高并发应用重构(写)填坑经验分享