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

news2024/11/25 11:07:16

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 np
    from concurrent.futures import ThreadPoolExecutor
    threadPool = 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() {
                @Override
                public 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.coloradmin.cn/o/1405570.html

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

相关文章

换上龙年表盘,开启一整年的好运

农历新年即将到来&#xff0c;华为表盘市场陆续推出一系列龙年主题的表盘。其中&#xff0c;三款表盘的设计格外引人注目&#xff1a;云白腾龙机械、非凡腾龙多色、玄武腾龙机械。 这三款表盘不仅在艺术审美上展现了设计师的独特创意与深厚功底&#xff0c;更是在细微之处巧妙融…

Tensorflow2.0笔记 - 范式norm,reduce_min/max/mean,argmax/min, equal,unique

练习norm,reduce_min/max,argmax/min, equal,unique等相关操作。 范数主要有三种&#xff1a; import tensorflow as tf import numpy as nptf.__version__#范数参考&#xff1a;https://blog.csdn.net/HiWangWenBing/article/details/119707541 tensor tf.convert_to_tensor(…

python系列-函数(上)

&#x1f308;个人主页: 会编程的果子君 ​&#x1f4ab;个人格言:“成为自己未来的主人~” 目录 函数 函数是什么 语法格式 函数参数 函数返回值 函数 函数是什么 编程中的函数和数学中的函数有一定的相似之处 编程中的函数&#xff0c;是一段可以被重复利用的代码片段…

Oracle Linux 9.3 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

Unity工程没有创建.sln文件,导致打开C#文件无法打开解决方案

最近又开始折腾些Unity的小项目&#xff0c;重新遇到一些常见的小问题 点击报错文件 却没有打开文件 于是查看了下打开Window->Package Manager 选择Unity Registry 搜索Visual Studio Editor&#xff0c;发现并没有安装 同理&#xff0c;也可以安装VSCode的插件 问题解决了…

【服务器】安装Docker环境

目录 &#x1f33a;【前言】 &#x1f33c;1. 打开Xshell软件 &#x1f33b;2. 安装Docker环境 ①&#xff1a;下载docker.sh脚本 ②&#xff1a;列出下载的内容 ③&#xff1a;执行一下get-docker.sh文件&#xff0c;安装docker ④&#xff1a;运行docker服务 ⑤&…

Python IO流

第一章、IO流 一、概述 1、IO流概念 2、IO流的分类 在Python中&#xff0c;I/O&#xff08;输入/输出&#xff09;流是处理数据输入和输出的机制。它们用于从文件、网络连接、内存等源读取数据&#xff0c;或将数据写入到这些目标中。I/O流以字节流和字符流的形式存在。 Pyth…

C++版QT:电子时钟

digiclock.h #ifndef DIGICLOCK_H #define DIGICLOCK_H ​ #include <QLCDNumber> ​ class DigiClock : public QLCDNumber {Q_OBJECT public:DigiClock(QWidget* parent 0);void mousePressEvent(QMouseEvent*);void mouseMoveEvent(QMouseEvent*); public slots:voi…

Git--创建仓库(1)

git init Git 使用 git init 命令来初始化一个 Git 仓库&#xff0c;Git 的很多命令都需要在 Git 的仓库中运行&#xff0c;所以 git init 是使用 Git 的第一个命令。 在执行完成 git init 命令后&#xff0c;Git 仓库会生成一个 .git 目录&#xff0c;该目录包含了资源的所有…

使用trace工具分析Mysql如何选择索引

背景说明 工作中,可能会遇到执行一个SQL,明明有索引,但是采用explain分析后发现执行结果并未走索引。甚至还有部分SQL语句相同就只是查询条件不一样也会出现有的走索引,有的不走索引情况。比如: 我的示例环境有个employees表,并有个idx_name_age_position的联合索引…

消息中间件之RocketMQ事务消息流程(二)

所谓事务消息就是基于消息中间件模拟的两阶段提交(2PC)&#xff0c;属于对消息中间件的一种特殊利用。总体思路如下: 1.系统A先向消息中间件发送一条预备消息(Half Message)&#xff0c;消息中间件在保存好消息之后向系统A发送确认消息 2.系统A执行本地事务 3.系统A根据本地事务…

Java毕业设计-基于ssm的学生社团活动管理系统-第82期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于ssm的学生社团活动管理系统&#xff1a;前端 jsp、jquery、ajax&#xff0c;后端 springmvc、spring、mybaties&#xff0c;角色分为管理员、学生、社团、用户&#…

html火焰文字特效

下面是代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>HTML5火焰文字特效DEMO演示</title><link rel"stylesheet" href"css/style.css" media"screen" type&quo…

即插即用篇 | UniRepLKNet:用于音频、视频、点云、时间序列和图像识别的通用感知大卷积神经网络 | DRepConv

大卷积神经网络(ConvNets)近来受到了广泛研究关注,但存在两个未解决且需要进一步研究的关键问题。1)现有大卷积神经网络的架构主要遵循传统ConvNets或变压器的设计原则,而针对大卷积神经网络的架构设计仍未得到解决。2)随着变压器在多个领域的主导地位,有待研究ConvNets…

C++ STL之list的使用及模拟实现

文章目录 1. 介绍2. list类的使用2.1 list类对象的构造函数2.2 list类对象的容量操作2.3 list类对象的修改操作2.4 list类对象的访问及遍历操作 3. list类的模拟实现 1. 介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭…

智慧工厂视频监控平台EasyCVR公网收流后内网设备无法播放是什么原因?

安防视频监控平台EasyCVR采用了开放式的网络结构&#xff0c;支持高清视频的接入和传输、分发&#xff0c;平台提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;此外&am…

OSPF基础华为ICT网络赛道

6.1.OSPF协议概述 由协议之中OSPF(Open Shortest Path First,开放式最短路径优先)协议是使用场 景非常广泛的动态路由协议之一。 OSPF在RFC2328中定义&#xff0c;是一种基于链路状态算法的路由协议。 静态路由是由工程师手动配置和维护的路由条目&#xff0c;命令行简单明确…

XTuner InternLM-Chat 个人小助手认知微调实践

1.概述 目标&#xff1a;通过微调&#xff0c;帮助模型了解对自己身份 方式&#xff1a;使用XTuner进行微调 2.实操 2.1微调环境准备 参考&#xff1a; XTuner复现-CSDN博客 # InternStudio 平台中&#xff0c;从本地 clone 一个已有 pytorch 2.0.1 的环境&#xff08;后…

Linux 部署mongodb

Linux 部署mongodb 一、mongodb安装包下载二、mongodb安装三、连接测试 linux tar包方式部署mongodb 一、mongodb安装包下载 查看系统版本与架构 cat /etc/redhat-release uname -mhttps://www.mongodb.com/download-center/community?jmpdocs下载对应操作系统版本 选择保存…

题记(26)--Sharing(链表公共后缀)

目录 一、题目内容 二、输入描述 三、输出描述 四、输入输出示例 五、完整C语言代码 一、题目内容 To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if…