Python中实现多任务的方式有3种
- 进程
- 线程
- 协程
A.进程能实现多任务,但是资源占用太多,是资源分配的单位,只有在计算密集型程序用多进程,计算密集型就是例如一个程序要计算1~1亿之间的每个数的立方和。
B.线程是操作系统的调度的单位,占用资源很少,只有在IO密集型程序下使用多线程,IO密集型就是相当于一个程序是tcp服务器,大部分时间都是在等待客户端的链接与数据的手法,一个进程中可以拥有N个线程,线程之间共享一些数据项全局变量。
C.协程最大的特点就是由开发者决定的,协程真正的意义是如果在一个任务执行的过程中,遇到了耗时操作,而不是等待耗时操作,而是去执行其他的任务,当这个耗时操作执行完毕之后,在切换回来,这样就提高了程序的效率。
关于任务的切换
cpu上下文Linux是一个多任务的操作系统,它支持远大于CPU数量的任务同时运行,当然,这些任务实际上并不是真正的在同时运行,而是应为系统在很短的时间内,将CPU轮流分配给他们,造成很多任务同时运行的错觉。在每个任务运行前,CPU都需要知道任务从哪里加载,又是从哪里开始运行,也就是说,需要系统事先帮他设置好CPU寄存器和程序计数器(Program Counter,PC)CPU寄存器,是CPU内置的容量小、但速度极快的内存。程序计数器,则是用来存储CPU正在执行的指令的位置,或者即将执行的下一条指令的位置。他们都是CPU在运行任何任务前,必须依赖的环境,因此也被叫做CPU上下文。知道了什么是CPU上下文,也就很容易理解CPU上下文切换。CPU上下文切换,就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文,到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
代码示例
from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
def coroutine_work(coroutine_name):
for i in range(10):
# print(coroutine_name, i)
print("--------1--------")
time.sleep(random.random())
def coroutine_work2(coroutine_name):
for i in range(10):
# print(coroutine_name, i)
print("--------2--------")
time.sleep(random.random())
# 方式1
# # 1. 创建
# g1 = gevent.spawn(coroutine_work, "work1")
# g2 = gevent.spawn(coroutine_work2, "work2")
# # 2. 等待协程结束
# g1.join()
# g2.join()
# 方式2:创建&等待合并操作
gevent.joinall([
gevent.spawn(coroutine_work, "work1"),
gevent.spawn(coroutine_work2, "work2")
])
进程 协程 线程对比
1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源很最大,效率很低
4.线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
5.协程切换任务资源很小,效率高
6.多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
全集解释器锁(GIL)
1.什么是GIL?
GIL:在一个进程中,任何一个线程想要执行,必须得到这个锁才能执行,这个锁称为GIL锁。
2.锁是什么?有什么作用?
锁是一种保护资源的一种方式,在很多地方都用,像数据库。
3.既然有了全局解释器锁为什么还有要互斥锁?
GIL仅仅保证了同一时刻有一个线程在运行,而互斥锁保证的是这个事情从头到尾做完。