一. 进程
进程的概念
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。 进程的概念: 进程是程序的一次执行过程, 正在进行的一个过程或者说一个任务,而负责执行任务的则是CPU. 进程的生命周期: 当操作系统要完成某个任务时,它会创建一个进程。当进程完成任务之后,系统就会撤销这个进程,收回它所占用的资源。从创建到撤销的时间段就是进程的生命期 进程之间存在并发性: 在一个系统中,同时会存在多个进程。他们轮流占用CPU和各种资源 并行与并发的区别: 无论是并行还是并发,在用户看来都是同时运行的,不管是进程还是线程,都只是一个任务而已, 真正干活的是CPU,CPU来做这些任务,而一个cpu(单核)同一时刻只能执行一个任务。 并行:多个任务同时运行,只有具备多个cpu才能实现并行,含有几个cpu,也就意味着在同一时刻可以执行几个任务。 并发:是伪并行,即看起来是同时运行的,实际上是单个CPU在多道程序之间来回的进行切换。 同步与异步的概念: 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进行处理,这样可以提高执行的效率。 比如:打电话的过程就是同步通信,发短信时就是异步通信。 多线程和多进程的关系: 对于计算密集型应用,应该使用多进程,因为可以使用多个CPU; 缺点是占用资源多。 对于IO密集型应用,应该使用多线程。线程的创建比进程的创建开销小的多。
创建进程
使用multiprocessing.Process
import multiprocessing import os ''' # 定义进程的第一种方式: 直接创建 # 定义一个进程函数 def fn(*args): # multiprocessing.current_process().name 查看当前进程的名字 print("子进程:",multiprocessing.current_process().name) print(args) if __name__ == "__main__": print("主进程:",multiprocessing.current_process().name) # 创建进程 p = multiprocessing.Process(target=fn,args=(1,2,3)) p.start() # 开启进程 '''
通过继承Process实现自定义进程
import multiprocessing import os # 定义进程的第二种方式: 通过继承实现 class MyProcess(multiprocessing.Process): def __init__(self): super().__init__() # 重写父类的run方法 def run(self): print("子进程:",multiprocessing.current_process().name) # 子进程的id print("子进程id:",os.getpid()) # 父进程的id print("父进程id:", os.getppid()) if __name__ == "__main__": print("主进程:",multiprocessing.current_process().name) p = MyProcess() p.start() print("p进程的id",p.pid) print("主进程的id",multiprocessing.current_process().pid)
同步异步和进程锁
import multiprocessing import time # 创建子进程函数 def fn(i,lock): with lock: print("子进程开始:",i) time.sleep(2) print("子进程结束:",i) if __name__ == "__main__": # 创建进程锁 lock = multiprocessing.Lock() for i in range(1,6): p = multiprocessing.Process(target=fn,args=(i,lock)) p.start()
使用Semaphore控制进程的最大并发(信号量)
import multiprocessing import random import time # 定义子进程函数 def fn(i,sem): with sem: print("子进程开始:",i) time.sleep(random.randint(1,4)) print("子进程结束:",i) if __name__ == "__main__": # 定义进程的最大并发数(信号量) sem = multiprocessing.Semaphore(5) for i in range(21): multiprocessing.Process(target=fn,args=(i,sem)).start()
协程
协程,又称微线程,纤程。英文名Coroutine。 首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,我们可以把一个协程切换到另一个协程,只要这个过程中保存或恢复CPU上下文那么程序还是可以运行的。 通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
协程和线程差异
协程的特点在于是一个线程执行, 那和多线程比,协程有何优势? 1.最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。 2.第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。 因为协程是一个线程执行,那怎么利用多核CPU呢? 最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。 协程的缺点: 它不能同时将CPU的多个核用上,只能使用一个核 Python对协程的支持是通过generator实现的。 在generator(生成器)中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。
协程的简单实现
import time # python中的协程的实现是通过生成器实现的 # C 函数中添加了yield关键词以后,变为了生成器函数,调用的时候不会直接执行.需要使用next()调用才可以输出C函数中的内容 def C(): while True: print("======haha=====") yield time.sleep(2) def D(c): while True: print("******lele******") next(c) time.sleep(2) if __name__ == "__main__": c = C() D(c)
使用协程
1.使用greenlet + switch实现协程调度
# 第一步:安装 greenlet pip install greenlet from greenlet import greenlet import time # 定义协程1 def fn1(): print("第一个协程") time.sleep(2) g2.switch() # 切换到第二个协程 print("飞流直下三千尺") time.sleep(2) g2.switch() # 切换到第二个协程 # 定义协程2 def fn2(): print("第二个协程") time.sleep(3) # 切换到协程1 g1.switch() print("疑是银河落九天") if __name__ == "__main__": # 创建协程1 g1 = greenlet(fn1) # 创建协程2 g2 = greenlet(fn2) g1.switch() ''' 第一个协程 第二个协程 飞流直下三千尺 疑是银河落九天
2.使用gevent +sleep自动将CPU执行权分配给当前未睡眠的协程
gevent 是第三方库, 是通过greenlet实现的协程,创建\调度的开销比线程还小,因此程序内部执行的效率高. pip install gevent import gevent # gevent + sleep 创建协程 def fn1(): gevent.sleep(2) print("协程1") gevent.sleep(4) print("床前明月光") def fn2(): gevent.sleep(1) print("协程2") gevent.sleep(6) print("地上鞋两双") if __name__ == "__main__": g1 = gevent.spawn(fn1) # 创建一个greentlet对象并切换 g2 = gevent.spawn(fn2) # 创建一个greentlet对象并切换 gevent.joinall([g1,g2]) # 将协程任务添加到事件循环中去 参数是一个任务列表
3.通过gvent+monkey调度 (常用)
import gevent from gevent import monkey monkey.patch_all() # 自动切换协程 # 上面的代码要放在程序的最上面 import requests def fn(url): print("协程:",url) res = requests.get(url) #print(url,len(res.text)) print(url,res.text) if __name__ == "__main__": urls = [ "https://www.baidu.com", "https://www.huya.com", "https://www.bilibili.com", "https://egame.qq.com/" ] # 定义一个空列表 接收所有的协程对象 g_list = [] for url in urls: g = gevent.spawn(fn,url) g_list.append(g) # 同时执行所有的协程 gevent.joinall(g_list)