Java/Python/Go不同开发语言在进程、线程和协程的设计差异

Java/Python/Go不同开发语言在进程、线程和协程的设计差异

  • 1. 进程、线程和协程上的差异
    • 1.1 进程、线程、协程的定义
    • 1.2 进程、线程、协程的差异
    • 1.3 进程、线程、协程的内存成本
    • 1.4 进程、线程、协程的切换成本
  • 2. 线程、协程之间的通信和协作方式
    • 2.1 python如何实现线程通信?
    • 2.2 java如何实现线程通信?
    • 2.3 go如何实现线程通信?
  • 3. 常用线程池的实现和使用方式
    • 3.1 python常用线程池
    • 3.2 java常用线程池
    • 3.3 go常用线程池
  • 4. 疑问和思考
    • 4.1 go语言中,协程的成本已经很低,还有必要使用线程池吗?
  • 5. 参考文档

在多线程项目开发时,最常用、最常遇到的问题是
1,线程、协程安全
2,线程、协程间的通信和控制

本文主要探讨不同开发语言go、java、python在进程、线程和协程上的设计和开发方式的异同。


1. 进程、线程和协程上的差异

1.1 进程、线程、协程的定义

  • 进程
    进程是操作系统进行资源分配的基本单位,每个进程都有自己的独立内存空间,不同的进程之间无法相互干扰。由于进程比较重,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

  • 线程
    线程又叫做轻量级进程,是进程的一个实体,是处理器任务调度和执行的基本单位位(能够申请到cpu资源执行相关任务)。它是比进程更小的能独立运行的基本单位。线程只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
    线程的执行需要申请对应的cpu资源,因此线程切换涉及CPU的资源切换(保存cpu上下文、触发软中断暂停当前线程、从就绪线程中选择一个执行),过程中会涉及用户态 -> 内核态(切换cpu)-> 用户态的切换,因此开销比较大。

  • 协程
    协程,又称微线程,是一种用户态的轻量级线程,协程的调度完全由用户控制(也就是在用户态执行)。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到线程的堆区,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,所以上下文的切换非常快(协程切换,线程不变,因此不需要切换cpu,不进行内核态切换,成本较低)。

进程、线程、协程之间的关系可以如下图诠释
在这里插入图片描述

1.2 进程、线程、协程的差异

线程进程的区别

  1. 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位,cpu运行任务是运行线程
  2. 资源开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一进程的线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
  3. 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。
  4. 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
  5. 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  6. 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。两者均可并发执行。

协程与线程的区别:

  1. 一个线程可以有多个协程。
  2. 大多数业务场景下,线程进程可以看做是同步机制,而协程则是异步。
  3. 线程是抢占式,而协程是非抢占式的,所以需要用户代码释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
  4. 协程并不是取代线程,而且抽象于线程之上。线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行。

1.3 进程、线程、协程的内存成本

进程占用内存

  • 32 位操作系统只支持 4G 内存的内存条,这是因为进程在 32 位操作系统中最多只能占用 4G 内存
  • 在 64 位操作系统中可以占用更多内存。

线程占用内存

  • 一般是 10MB,不同的操作系统版本之间有些差异,区间在 4M - 64M。

协程占用内

  • 一个协程占用 2KB 左右的内存

内存占用: 进程 >> 线程 >> 协程

更低的内存占用代表着更低的资源切换成本和可以提供更高的并发。

1.4 进程、线程、协程的切换成本

不同的进程享有独立的资源,因此进程切换,需要执行如下2个步骤

  1. 切换页目录以使用新的地址空间(切换虚拟内存空间)
  2. 切换内核栈和硬件上下文(切换cpu资源)

相同进程的线程共享相同的内存,因此切换线程

  1. 使用的是进程的内存资源,不需要切换虚拟内存空间
  2. 切CPU换上下文时,需要耗费 CPU 时间,但是进程切换的开销相差不大(几微秒)。

相同线程的协程使用相同的内存和cpu资源,因此协程切换

  1. 在用户空间发生,不需要切换cpu,只需要切换简单CPU寄存器状态
  2. 一次协程的上下文切换最多需要几十纳秒的时间。

切换成本: 进程切换 > 线程切换 > 协程切换

2. 线程、协程之间的通信和协作方式

线程、协程之间的通信主要用于2个目的

  • 控制线程、协程的执行顺序(触发条件、逻辑启停等)
  • 线程、协程之间传递信息,用于在不同线程、协程之间实现业务逻辑
  • 感知子线程、协程是否已经执行完成

注意,
如果不同的线程进行在操作时,需要注意变量的线程安全问题

  • 如果使用的的对象是线程安全的,不需要加锁保护,但是需要注意多个线程使用相同的对象以及相关对象的性能问题
  • 如果使用的对象不是线程安全的,注意进行保护。

2.1 python如何实现线程通信?

通常使用如下方法进行线程同步,可以根据实际情况调整

  • 共享变量
  • queue队列

更多可以参考 python的多线程及线程间的通信方式

2.2 java如何实现线程通信?

通常使用如下方法进行线程同步,可以根据实际情况调整

  1. 锁与同步
  2. 等待/通知机制
  3. 信号量
  4. 管道

更多可以参考 Java线程间的通信

2.3 go如何实现线程通信?

在go中,常用的是协程(goroutine)进行多并发,因此探讨的通信方式都是以协程(goroutine)进行讨论。

实现多个goroutine间的同步与通信大致有:

  • 全局共享变量
  • channel通信(CSP模型)
  • Context包

这3种方法具体实现可以参考文档 深入golang之—goroutine并发控制与通信

3. 常用线程池的实现和使用方式

3.1 python常用线程池

线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。

由于全局GIL锁存在,python多线程本质上同一时间只能1个线程在执行,并不能高效的利用所有的CPU核心。
1, 如果使用多线程,线程的类型基本都是IO密集型,线程进入IO等到时会自动释放GIL索引,因此GIL锁的存在对于这种类型的计算性能影响不算大
2,如果使用多线程,线程的类型基本都是CPU密集型,只能等待解释器不间断运行了1000字节码(Py2)或运行15毫秒(Py3)后,该线程也会放弃GIL,切换到其他的线程执行。

使用线程池来执行线程任务的步骤如下:

  1. 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
  2. 定义一个普通函数作为线程任务。
  3. 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
  4. 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。
def test(value1, value2=None):print("%s threading is printed %s, %s"%(threading.current_thread().name, value1, value2))time.sleep(2)return 'finished'def test_result(future):print(future.result())if __name__ == "__main__":import numpy as npfrom concurrent.futures import ThreadPoolExecutorthreadPool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="test_")for i in range(0,10):future = threadPool.submit(test, i,i+1)threadPool.shutdown(wait=True)

更多使用参考PYTHON线程池及其原理和使用(超级详细)

3.2 java常用线程池

常用4中类型的线程池

  • newFixedThreadPool
    构造函数
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

从构造方法可以看出,它创建了一个固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大值nThreads。线程池的大小一旦达到最大值后,再有新的任务提交时则放入无界阻塞队列中,等到有线程空闲时,再从队列中取出任务继续执行。

  • newCachedThreadPool
    构造函数
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

从构造方法可以看出,它创建了一个可缓存的线程池。当有新的任务提交时,有空闲线程则直接处理任务,没有空闲线程则创建新的线程处理任务,队列中不储存任务。线程池不对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。如果线程空闲时间超过了60秒就会被回收。(使用方法不是非常推荐)

  • newSingleThreadExecutor
    构造函数
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

从构造方法可以看出,它创建了一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行,无法指定最大线程池数量。(使用方法不是非常推荐)

  • newScheduledThreadPool
    构造函数
public class OneMoreStudy {public static void main(String[] args) {final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);System.out.println("提交时间: " + sdf.format(new Date()));scheduledThreadPool.schedule(new Runnable() {@Overridepublic void run() {System.out.println("运行时间: " + sdf.format(new Date()));}}, 3, TimeUnit.SECONDS);scheduledThreadPool.shutdown();}
}

这个方法创建了一个固定大小的线程池,支持定时及周期性任务执行。创建并执行ScheduledFuture,该ScheduledFuture在指定的延迟后启用,任务立即提交给线程池,线程池安排线程在指定时间后正式开始运作,运作以后保持正常节奏(类似调度任务)

根据使用习惯选择合适的方法类,更多可以参考Java中常用的四种线程池

3.3 go常用线程池

go的基础方法类中没有实现线程池,需要自己实现,或者引入第三方库进行实现。

4. 疑问和思考

4.1 go语言中,协程的成本已经很低,还有必要使用线程池吗?

梳理常用的开发语言中,是有已经有了现成的线程池方法(类)提供使用,情况如下:

开发语言是否支持线程池备注
python
java
go可以引用第三方的库或者自己实现

go的协程已经把单个协程的成本降低到足够低,还有必要设计线程池吗?该问题在Go Forum 中 skillian 做了解答。

我引用回复

Like lutzhorn said: Need? No.But for some workloads in some projects, it might make sense to have a general worker pool implementation. The benefit is that the memory consumption can be limited by not allowing the number of goroutines to exceed whatever the pool allows, though I’m unsure of what order of magnitude of goroutines you need before that benefit is manifested.Francesc Campoy created a fractal with 4 million goroutines (link 55) and it worked and scaled, but not perfectly. The issue wasn’t with the number of goroutines but that the runtime spent more time managing the goroutines than the goroutines actually worked. By giving the goroutines more work, (I think instead of each goroutine processing only one pixel, they processed the whole line?) the solution still scaled and ended up performing better.

翻译过来就是
1, 通常不需要
2, 除了特殊场景,特殊项目上,线程池是有意义的。这样做的好处是,可以通过不允许超过池允许的程序的数量来限制内存消耗,尽管我不确定在显示出这种好处之前需要多少量级的程序。

5. 参考文档

  • 一文快速了解进程、线程与协程
  • 进程、线程以及协程的区别
  • 深入golang之—goroutine并发控制与通信
  • Java线程间的通信

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/641350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型实战营Day6 笔记

本期主题为&#xff1a; 为何测评&#xff0c;因场景众多&#xff0c;需要统一的标准&#xff1a; 评测的意义&#xff1a; 传统NLP的一些评测需要&#xff1a; 到了大模型时代&#xff0c;需要评测的就更多了&#xff1a; 客观评测&#xff1a; 有些主观题可以用模型评价…

cdh6.3.2的hive配udf

背景 大数据平台的租户要使用udf&#xff0c;他们用beeline连接&#xff0c; 意味着要通过hs2&#xff0c;但如果有多个hs2&#xff0c;各个hs2之间不能共享&#xff0c;需要先把文件传到hdfs&#xff0c;然后手动在各hs2上create function。之后就可以永久使用了&#xff0c;…

docker配置node项目

首先在项目根目录创建Dockerfile FROM node:18.19RUN mkdir /appCOPY . /appWORKDIR /appRUN npm installEXPOSE 8081CMD ["npm","run","start"]添加.dockerignore文件 /dist /node_moduleslogs *.log npm-debug.log* yarn-debug.log* yarn-er…

从零开始的OpenGL光栅化渲染器构建3-法线贴图和视差贴图

前言 我们可以用一张纹理贴图来表现物体表面的基础反射颜色&#xff0c;也可以用一张镜面反射贴图&#xff0c;来指派表面是否产生高光。除此之外&#xff0c;我们可以用贴图来存储表面的法线信息&#xff0c;以及高度信息&#xff0c;从而让渲染效果更加精细。 法线贴图 我…

通过浏览器URL地址,5分钟内渗透你的网站!很刑很可拷!

今天我来带大家简单渗透一个小破站&#xff0c;通过这个案例&#xff0c;让你深入了解为什么很多公司都需要紧急修复各个中间件的漏洞以及进行URL解析拦截等重要操作。这些措施的目的是为了保护网站和系统的安全性。如果不及时升级和修复漏洞&#xff0c;你就等着被黑客攻击吧&…

浮点数详解

目录 1.概述 2.浮点数的编码方式 2.1.float类型的IEEE编码 2.2.double类型的IEEE编码 2.3.现场问题 2.4.总结 1.概述 计算机也需要运算和存储数学中的实数。在计算机的发展过程中&#xff0c;曾产生过多种存储实数的方式&#xff0c;有的现在已经很少使用了。不管如何存储…

LeetCode 48 旋转图像

题目描述 旋转图像 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4…

Vue.js 3 项目开发:迈向现代化前端开发的必经之路

文章目录 一、Vue.js 3简介二、Vue.js 3新特性1. Composition API2. 更好的性能3. 更好的TypeScript支持4. 更多的生命周期钩子5. 更好的自定义指令API 三、Vue.js 3项目开发实践1. 搭建开发环境2. 项目结构规划3. 组件开发4. 路由管理5. 状态管理6. 测试与部署 《Vue.js 3企业…

工业计算机应用——物流行业

工业计算机在物流行业的应用 随着全球化和电商的快速发展,物流行业已经成为现代经济体系中的重要支柱。在这个高度自动化的行业中,工业计算机扮演着至关重要的角色。本文将深入探讨工业计算机在物流行业的应用及其优势。 一、工业计算机在物流行业的应用场景 仓储管理工业计…

Viessmann Vitogate RCE漏洞复现(CVE-2023-45852)

0x01 产品简介 Viessmann Vitogate 300是用于将Viessmann LON连接到BACnet或Modbus的网关。 0x02 漏洞概述 Vitogate 300 组件/cgi-bin/vitogate.cgi中的一个问题允许未经身份验证的攻击者绕过身份验证&#xff0c;通过特制的请求执行任意命令&#xff0c;可导致服务器失陷。…

Linux如何将文件或目录打成rpm包? -- fpm打包详解

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

【江科大】STM32:串口HEX/文本数据接收和发送(代码部分)(下)

串口发送 #include "stm32f10x.h" // Device header#include<stdio.h> #include<stdarg.h> void Serial_Init(void) {RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPI…

latex画边框框加粗的表格

示例代码 \begin{table}[H]% 这四个距离分别控制toprule和buttomrule的上、下空白间距&#xff0c;如果不是0的话以后竖线和横线连不起来\abovetopsep0pt\aboverulesep0pt\belowrulesep0pt\belowbottomsep0pt\centering% \heavyrulewidth是\toprule和\bottomrule的默认宽度&am…

如何做好培训管理?推荐使用这款培训管理系统(内附详细步骤+免费模板)

本文将为大家讲解&#xff1a;1、如何做好培训管理&#xff1f;2、如何使用零代码平台搭建培训管理系统&#xff1f; 培训管理&#xff0c;作为企业人力资源管理的核心环节&#xff0c;对于确保员工具备完成任务所需的专业知识和技能发挥着至关重要的作用。它不仅是提升员工绩…

北斗卫星为野外科考人员提供安全保障

北斗卫星为野外科考人员提供安全保障 自第二次青藏高原综合科学考察研究启动以来&#xff0c;青海不断提升科考服务保障能力&#xff0c;推动科考全程信息化&#xff0c;有效促进科考成果转化。 为保障科考人员的人身安全&#xff0c;青海省青藏科学考察服务中心开发了基于北…

第08章_面向对象编程(高级)(static,单例设计模式,理解mian方法,代码块,final,抽象类与抽象方法,接口,内部类,枚举类,注解,包装类)

文章目录 第08章_面向对象编程(高级)本章专题与脉络1. 关键字&#xff1a;static1.1 类属性、类方法的设计思想1.2 static关键字1.3 静态变量1.3.1 语法格式1.3.2 静态变量的特点1.3.3 举例1.3.4 内存解析 1.4 静态方法1.4.1 语法格式1.4.2 静态方法的特点1.4.3 举例 1.5 练习 …

UI设计中的插画运用优势(下)

6. 插画赋予设计以美学价值&#xff0c;更容易被接受 即使所有人都在分析和争论产品的可用性和易用性&#xff0c;大家在对美的追求上&#xff0c;始终保持着一致的态度。一个设计是否具备可取性&#xff0c;是否能够通过甲方、客户和实际用户&#xff0c;是每个设计人都需要面…

高频一体式读写器的应用及其原理

高频一体式读写器作为一款读写设备&#xff0c;将RFID读写模块和天线集于一体&#xff0c;通过天线与RFID标签进行无线通信&#xff0c;实现对标签的识别和内存数据的读出或写入操作。具备安全、准确、快速、扩展、兼容性强等特点&#xff0c;具备非接触识别、远距离识别、环境…

Laravel 10.x 里如何使用ffmpeg

原理上很简单&#xff0c;就是使用命令行去调用ffmpeg&#xff0c;然后分析一下输出是不是有错误。 安装 首先安装 symfony/process&#xff0c;主要用于包装一下&#xff0c;用来代替 exec, passthru, shell_exec and system 。 composer require symfony/process composer…

PowerShell install 一键部署grafana

grafana 前言 Grafana 是一款开源的数据可视化和监控仪表盘工具。它提供了丰富的数据查询、可视化和报警功能,可用于实时监控、数据分析和故障排除等领域。 通过 Grafana,您可以连接到各种不同的数据源,包括时序数据库(如 Prometheus、InfluxDB)和关系型数据库(如 MySQ…