浅谈进程,线程,协程以及服务端高并发的处理

2024-08-23 02:04

本文主要是介绍浅谈进程,线程,协程以及服务端高并发的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

进程、线程、协程

  • 进程:独立的程序实例,资源开销较大,适合隔离性要求高的任务。

    • 独立性:进程具有独立的内存空间和资源,互不干扰。

    • 资源开销大:由于每个进程都需要分配独立的内存和资源,创建和切换进程的开销相对较大。

    • 进程间通信复杂:进程之间的通信通常需要通过操作系统提供的机制,如管道、消息队列或共享内存

  • 线程:进程中的执行单元,轻量级,适合需要共享资源的并发任务。

    • 共享内存空间:同一进程中的所有线程共享相同的内存地址空间,因此线程之间的通信比进程之间更容易。

    • 轻量级:线程创建和切换的开销比进程小,因为线程间共享资源,不需要像进程那样进行大量的上下文切换。

    • 并发执行:在多核处理器上,不同线程可以真正实现并行运行。

  • 协程:比线程更轻量的并发执行单元,适合 I/O 密集型任务和对性能要求较高的场景。

    • 轻量级:协程比线程更轻量,通常不需要像线程那样的上下文切换开销,因为它们在用户态完成调度。

    • 非抢占式多任务:协程的切换是显式的,只有当协程主动让出执行权时,才会切换到其他协程。这使得协程之间的并发执行更加可控。

    • 无需多线程环境:协程在单线程环境中就能实现高效的并发,因此非常适合 I/O 密集型操作,如网络请求或文件读取。

进程出现的目的,是为了更好的利用CPU资源。

例如:

假设有两个任务A和B,当A遇到IO操作,CPU默默地等待任务A读取完操作再去执行任务B,这样无疑是对CPU资源的极大的浪费。若在任务A读取数据时,让任务B执行,当任务A读取完数据后,再切换到任务A执行,这样就可以更好地利用CPU资源。这里的切换涉及到状态的保存,状态的恢复,需要有一个东西去记录任务A和任务B分别需要什么资源,怎样去识别任务A和任务B,这时进程就出现了。

因此,通过进程来分配系统资源,标识任务。

如何分配CPU去执行进程称之为调度,进程状态的记录,恢复,上下文切换(简称切换)。

其次,若上面提及的任务A是一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。

若只有一个进程,会造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西-------文本内容,不停地切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。这时线程出现了。

线程共享进程的大部分资源,并参与CPU的调度。

当操作系统切换线程时,需要保存当前线程的状态(如寄存器、程序计数器、堆栈指针等),然后加载下一个线程的状态,这个过程被称为上下文切换。线程上下文切换通常涉及系统调用和内核参与,因为线程通常由操作系统内核来管理。

假设当涉及到大规模的并发请求连接时,例如有一万个人同时连接我的服务器,但系统资源有限,如果以线程作为处理单元,调内部系统资源的话大部分线程都处于等待状态

传统的请求是线程池+一个请求创建一个线程 (java应用服务器tomcat,Jetty) 由于是这种方式有很大的并发限制所以有了如下的优化:

  • NIO 提供了非阻塞的 I/O 操作,这使得线程不需要等待 I/O 操作完成,而是可以在 I/O 准备好时被通知。提高了线程利用率。由于一个线程可以管理多个通道,NIO 在高并发场景下能显著减少线程数量,从而提高性能。

  • 线程池,通常使用线程池管理线程。线程池限制了同时运行的线程数,并通过复用线程来减少资源消耗。

局限性:

  • 资源开销:每个线程都占用一定的系统资源,包括内存、CPU 时间、操作系统调度等。当有大量线程(如一万个线程)同时运行时,系统的内存和 CPU 资源很快就会被耗尽。

  • 上下文切换:当多个线程在 CPU 上并发运行时,操作系统内核需要频繁地进行上下文切换,保存和恢复每个线程的状态。这种上下文切换是有开销的,尤其是在大量线程之间频繁切换时,会导致系统性能下降,很多时间可能都浪费在切换上下文上,而不是实际处理任务。

协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在IO上的性能瓶颈。

使用协程就可以实现线程自己调度,不陷入内核级别的上下文切换。协程可以在 I/O 操作时主动让出 CPU,线程不会被阻塞,其他协程可以继续执行。这样,单个线程可以管理多个协程,从而极大地提高了并发处理能力,充分利用 CPU 资源。

协程切换是在用户态完成的,不涉及系统调用。协程之间的切换通常只需要保存和恢复少量的状态信息,切换开销极小,因此在高并发场景下,协程的性能优势非常明显。

为什么协程不需要经过内核级别的上下文切换,我是这样认为的:

内核态 vs. 用户态:操作系统运行在两种模式下:内核态和用户态。内核态拥有对硬件和系统资源的完全控制,用户态则是普通应用程序运行的状态。线程切换通常需要切换到内核态进行调度,协程的调度和切换不需要通过系统调用来进行,完全在用户态操作。这意味着协程切换不会触发内核级别的上下文切换,不涉及操作系统的调度器,也不需要保存和恢复操作系统管理的线程栈和寄存器状态等。

进程和线程都是操作系统自带的,协程是有些程序原生支持的,例如go,lua, 有些是后期版本才有的,比如python2.5 以后

Python:Python 的 asyncio 库是一个常用的协程框架,可以轻松创建和管理协程。使用 asyncio,一个线程可以处理很多的并发任务,例如网络 I/O、数据库查询等。

JavaScript:JavaScript 的异步操作(如 Promise 和 async/await)允许在单线程环境中实现协程式的并发处理。Node.js 就利用了这种模型,允许在单线程中处理大量并发 I/O 操作。

Python 协程的举例:

异步IO操作:

import asyncio
import aiohttpasync def download_file(session, url):async with session.get(url) as response:content = await response.read()filename = url.split('/')[-1]with open(filename, 'wb') as f:f.write(content)print(f"Downloaded {filename}")async def main():urls = ['http://example.com/file1','http://example.com/file2','http://example.com/file3']async with aiohttp.ClientSession() as session:tasks = [download_file(session, url) for url in urls]await asyncio.gather(*tasks)asyncio.run(main())

并发任务的协调与管理

import asyncioasync def task(name, delay):await asyncio.sleep(delay)print(f"Task {name} completed.")return nameasync def main():# A,B并发执行 两个协程task_a = asyncio.create_task(task("A", 3))task_b = asyncio.create_task(task("B", 2))await asyncio.gather(task_a, task_b)# Task C 在A,B后执行 C 可能依赖AB 的结果await task("C", 1)asyncio.run(main())

await 关键字用于暂停协程的执行,直到 await 后面的协程对象完成。它使得协程可以异步地等待其他协程的结果,而不会阻塞事件循环。

服务端高并发处理

对于服务端提高并发常见的处理方案

常见的系统架构图

1. 负载均衡

  • 水平扩展(Scale-out):通过增加服务器数量,使用负载均衡器(如Nginx)将请求分发到多台服务器上,从而分散压力。负载均衡器可以基于不同的策略(如轮询、最小连接数、IP哈希等)将流量分配给各个后端服务器。

2. 缓存技术

  • 数据缓存:使用缓存机制(如Redis、Memcached)缓存热点数据,减少数据库的查询次数。常见的缓存策略有本地缓存、分布式缓存等。

  • 页面缓存:对于动态生成的页面,尤其是变化不频繁的页面,可以将其缓存一段时间,从而减少服务器的计算负载。

3. 数据库优化

  • 读写分离:通过主从复制,将读操作分配到从数据库,写操作由主数据库处理,从而减轻主数据库的负载。

  • 分库分表:当单一数据库实例无法承载大量数据时,可以将数据按某种规则拆分到多个数据库(分库)或多个表(分表)中。

  • 索引优化:合理使用数据库索引,加速查询速度,同时避免过多的索引导致的写操作性能下降。

  • 使用NoSQL数据库:在某些场景下(如处理海量数据、高频写入等),可以使用NoSQL数据库(如MongoDB、Cassandra、HBase等)替代传统的关系型数据库。

4. 异步处理与消息队列

  • 异步处理:将非实时任务(如日志记录、通知发送、复杂计算)通过异步方式处理,减少主线程的负载。可以使用事件驱动或基于回调的异步编程模型(如Node.js、Python的异步IO)。

  • 消息队列:使用消息队列(如RabbitMQ、Kafka、ActiveMQ)解耦各个系统组件,将需要排队的任务放入队列中,异步处理,从而避免系统过载。

5. 服务拆分与微服务架构

  • 服务拆分:将单一的大型应用程序拆分为多个独立的服务,减少耦合,增强系统的可扩展性。每个服务可以独立扩展并优化,从而提高系统整体的并发处理能力。

  • 微服务架构:采用微服务架构,将不同的业务逻辑分离成独立的微服务,通过RESTful API或消息队列通信,增强系统的灵活性和伸缩性。

6. 限流与降级

  • 限流:对接口或服务设置访问频率限制,防止因瞬时流量过大导致系统崩溃。可以采用令牌桶算法、漏桶算法等限流策略。

  • 熔断与降级:当系统部分组件过载或不可用时,及时熔断(停止调用)该组件,并执行降级策略(如返回默认值、提供备用服务),以保证核心功能的正常运行。

7. 操作系统与Web服务器调优

  • 操作系统调优:调整操作系统的网络参数(如文件描述符上限、TCP连接数等),优化内存管理,减少上下文切换,提升并发处理能力。

  • Web服务器调优:调整Web服务器(如Nginx、Apache、Tomcat等)的并发连接数、线程池大小、缓存配置等参数,以适应高并发环境。

8. 前端优化

  • 减少请求数 减少服务器压力。

  • 延迟加载:使用Lazy Load技术,推迟加载非关键资源,以减少页面加载时的并发请求数。

这篇关于浅谈进程,线程,协程以及服务端高并发的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1098036

相关文章

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

Java Response返回值的最佳处理方案

《JavaResponse返回值的最佳处理方案》在开发Web应用程序时,我们经常需要通过HTTP请求从服务器获取响应数据,这些数据可以是JSON、XML、甚至是文件,本篇文章将详细解析Java中处理... 目录摘要概述核心问题:关键技术点:源码解析示例 1:使用HttpURLConnection获取Resp

Python如何精准判断某个进程是否在运行

《Python如何精准判断某个进程是否在运行》这篇文章主要为大家详细介绍了Python如何精准判断某个进程是否在运行,本文为大家整理了3种方法并进行了对比,有需要的小伙伴可以跟随小编一起学习一下... 目录一、为什么需要判断进程是否存在二、方法1:用psutil库(推荐)三、方法2:用os.system调用

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Java实现优雅日期处理的方案详解

《Java实现优雅日期处理的方案详解》在我们的日常工作中,需要经常处理各种格式,各种类似的的日期或者时间,下面我们就来看看如何使用java处理这样的日期问题吧,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言一、日期的坑1.1 日期格式化陷阱1.2 时区转换二、优雅方案的进阶之路2.1 线程安全重构2

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Python处理函数调用超时的四种方法

《Python处理函数调用超时的四种方法》在实际开发过程中,我们可能会遇到一些场景,需要对函数的执行时间进行限制,例如,当一个函数执行时间过长时,可能会导致程序卡顿、资源占用过高,因此,在某些情况下,... 目录前言func-timeout1. 安装 func-timeout2. 基本用法自定义进程subp