说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
所谓多任务一般是指在系统执行多个任务,这些任务的特点或是过程复杂,或是任务量大,或是用时长。Python 给大家的印象是在处理任务性能方面不尽人意,但是随着近些年 Python 版本的迭代,加入了许多特性正在弥补这方面的不足。本内容就针对 Python 在执行多任务方面的特性进行介绍。
Python 实现多任务主要能力有:
todo:编写中
我们首先要了解并发和并行这两个概念,它们都是 CPU 执行程序时的机制和策略。现在,多核CPU已经非常普及了,但是,即使过去的单核 CPU,也可以执行 多任务。由于 CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
其实就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务 2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。 表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太 快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核 心上执行。
例如,一个多任务表现形式的例子。window 下打开任务管理器可以很清晰看到多个进程在同时执行任务,qq、微信等都是以进程的形式寄存在window下。大多我们在写一些控制台程序真正执行的时候都是以进程调度。
因此:
区别在任务是否在真正的同时进行。
总结:
常见的程序计算类型有:
程序需要执行较多的读写、请求和回复任务的需要大量的IO操作,IO密集型操作使用并发更好。
进程是程序在某个数据集上的执行, 即进程是程序的一次执行,是一个动态的实体,有自己的生命周期,因创建而产生、因调度而运行、因等待资源或事件而被处于等待状态、因任务完成而被撤销,反映的是一个程序在一定的数据集上运行的全部动态过程。
无论系统有几个CPU,每个进程运行在单个CPU上,如果单CPU有多个进程则多个进程并发执行。
线程是进程的一个实体,是 CPU 调度的单位 ,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点运行中必不可少的资源(如:程序计数器、一组寄存器和栈)。
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程是程序执行的最小单元,每个程序都至少有一个线程,若程序只有一个线程,那就是它程序本身。单线程的进程可以简单地理解为只有一个线程的进程,一个进程在同一时间只做一件事。多线程的进程可以理解为一个进程同一时间段做多件事,每个线程可以处理不同的事务,一个线程阻塞不会影响另一个线程。
协程,又称微线程,是一种用户态的轻量级线程。协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置,当程序中存在大量不需要CPU的操作时(IO),适用于协程。
协程有极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。
不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,所以想要利用多核CPU,最简单的方法是多进程+协程,这样既充分利用多核,又充分发挥协程的高效率。
那符合什么条件就能称之为协程:
进程和线程之间的关系: 举个简单的例子:一个手机中运行了好多后台的APP程序,如微信、QQ、支付宝…,其中,一个进程就是运行中的QQ,而QQ中可以跟不同的人进行聊天,每个聊天的窗口就是一个线程,你可以同时跟好多人聊天(即,开好多个聊天窗口,也就是说一个进程中可以由好多线程 ),但是当一个聊天窗口卡死了,QQ就不能运行了(一个线程死掉就等于整个进程死掉 ),只能强制把它关了然后重启,但是你QQ挂了,并不影响你的微信和支付宝的运行(进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响 ),同时你可以在不同的聊天窗口发送相同的QQ表情包,但是你不能在微信里发送QQ里的表情包(同进程里的多线程之间共享内存数据,不同进程之间是相互独立的,各有个的内存空间 )。
对比:
一般的术语中,parallelism 并行指的是一种思想,表示同时做一件事。而concurrency并发指的是为了实现并行的解决方案。所以其实,我们讨论的,无论多线程多进程也或者多核,其实讨论的全部是concurrency。
实质没区别,因为:
通俗来讲,就是两个线程到底做不做内存隔离。有人说,在 Python 中多线程只是个美丽的梦。
进程 | 线程 | 协程 | |
---|---|---|---|
概念 | - 一段程序的执行过程 - 是资源分配的最小单位 |
操作系统(内核)运算调度的最小单位 | 用户态实现的运算调度单位 可暂停和恢复执行的过程(函数) |
独立资源 | - 虚拟内存空间(代码段、数据段、堆栈等) - 内核栈、thread_info、task_struct(pid,tgid等) - 寄存器组的值 |
- 线程id(pid) - 堆栈 - 寄存器组的值 |
- 栈 - 部分栈切换的寄存器 |
切换代价 | 高 | 中 | 低 |
通信(同步)方式 | 1. 管道/匿名管道 2. 有名管道 3. 信号 4. 消息队列 5. 共享内存 6. 信号量 7. 套接字 |
1. 锁机制 2. 信号量机制 3.信号机制 |
Future,channel, pub/sub等 |
优点 | 1. 进程间相互独立,一个进程出了问题不会影响其它进程 2. 可以利用多CPU的资源 |
1. 线程之间共享内存和变量,通信比较方便 2. 程序逻辑和控制方式简单 3. 上下文切换资源消耗中等 |
1. 无须原子操作锁定及同步的开销 2. 上下文切换资源小 3. 高并发性、高扩展性、低成本 |
缺点 | 1. 需要跨进程边界,当数据交流大是开销比较高 2. 创建、上下文切换开销大 |
1.线程之间的同步和加锁控制比较麻烦 2. 一个线程的崩溃可能影响到整个程序的稳定性 |
1. 不能使用多核资源 |
适用场景 | CPU密集型操作(如科学计算) | 1. 多核CPU 2. I/O密集操作(网络I/O,磁盘I/O) |
单核CPU I/O密集型操作(网络I/O,如秒杀系统,RPC服务器,即时通讯等) |
GIL 是 CPython 解释器所采用的一种机制,它确保同一时刻只有一个线程在执行 Python bytecode。此机制通过设置对象模型(包括 dict 等重要内置类型)针对并发访问的隐式安全简化了 CPython 实现。给整个解释器加锁使得解释器多线程运行更方便,其代价则是牺牲了在多处理器上的并行性。
不过,某些标准库或第三方库的扩展模块被设计为在执行计算密集型任务如压缩或哈希时释放 GIL。此外,在执行 I/O 操作时也总是会释放 GIL。
创建一个(以更精细粒度来锁定共享数据的)“自由线程”解释器的努力从未获得成功,因为这会牺牲在普通单处理器情况下的性能。据信克服这种性能问题的措施将导致实现变得更复杂,从而更难以维护。
由于C语言底层原因,CPython 中多线程运行,每个线程都需要申请全局资源,但 是 Cpython 并不能应对所有线程同时的资源请求,为防止发生错误,对所有线程申请全局资源的时候增加了限制--全局解释器锁。
在上世纪 90 年代单核 CPU 世界里,多线程主要为了一边做 IO,一边做 CPU 密集型任务设计的,GIL 设计简单,并不会影响性能。进入 2010 年以后,变成了多核的世界,可以同时做多个 CPU 密集型任务, GIL才真正变成问题。但是因为 CPython 实在太火了,这些年无数的优秀库是基于 CPython(也就是我们现在见到的最主流的 CPython 实现)的。所以无数的尝试发现,要在不打破 9 0年代延续下来的 C API 的前提下去除 GIL 基本不可能。所以我们看到了一次次的尝试的失败。
另外两个版本的 Python 实现 Jython 和 IronPython 都是没有 GIL 的,但支持的库太少。
如何避免GIL的影响:
另外还需要的依赖知识有:
更新时间:2024-07-01 15:14:22 标签:python 性能 多任务