本文共 7043 字,大约阅读时间需要 23 分钟。
在介绍进程、线程和协程时,先介绍多任务、并发与并行
多任务
from multiprocessing import Process import time def run_proc(): """子进程要执行的代码""" while True: print("----2----") time.sleep(1) if __name__=='__main__': p = Process(target=run_proc) p.start() while True: print("----1----") time.sleep(1)
Process 语法结构
Process([group [, target [, name [, args [, kwargs]]]]])
注意:进程间不共享全局变量
进程间通信(Queue)
可以使用 multiprocessing 模块的 Queue 实现多进程之间的数据传递,Queue 是一个消息的队列
from multiprocessing import Queue q=Queue(3) #初始化一个Queue对象,最多可接收三条put消息 q.put("消息1") q.put("消息2") print(q.full()) #False q.put("消息3") print(q.full()) #True #因为消息列队已满下面的try都会抛出异常,第一个try会等待2秒后再抛出异常,第二个Try会立刻抛出异常 try: q.put("消息4",True,2) except: print("消息列队已满,现有消息数量:%s"%q.qsize()) try: q.put_nowait("消息4") except: print("消息列队已满,现有消息数量:%s"%q.qsize()) #推荐的方式,先判断消息列队是否已满,再写入 if not q.full(): q.put_nowait("消息4") #读取消息时,先判断消息列队是否为空,再读取 if not q.empty(): for i in range(q.qsize()): print(q.get_nowait())
初始化 Queue() 对象时(例如:q=Queue()), 若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头)
进程池(Pool)
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务,请看下面的实例:
from multiprocessing import Pool import os, time, random def worker(msg): t_start = time.time() print("%s开始执行,进程号为%d" % (msg,os.getpid())) # random.random()随机生成0~1之间的浮点数 time.sleep(random.random()*2) t_stop = time.time() print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start)) po = Pool(3) # 定义一个进程池,最大进程数3 for i in range(0,10): # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,)) # 每次循环将会用空闲出来的子进程去调用目标 po.apply_async(worker,(i,)) print("----start----") po.close() # 关闭进程池,关闭后po不再接收新的请求 po.join() # 等待po中所有子进程执行完成,必须放在close语句之后 print("-----end-----")
进程池中的 Queue
如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会得到一条如下的错误信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.
线程
import threading import time def download_music(): """模拟下载歌曲,需要5秒钟下载完成""" for i in range(5): time.sleep(1) # 休眠1秒 print("---正在下载歌曲%d---" % i) def play_music(): """模拟播放歌曲,需要5秒钟下载完成""" for i in range(5): time.sleep(1) # 休眠1秒 print("---正在播放歌曲%d---" % i) def main(): # 创建线程对象t1 # target: 指向新开启的线程要执行的代码 t1 = threading.Thread(target=download_music) t2 = threading.Thread(target=play_music) t1.start() # 启动线程,既然线程开始执行 t2.start() if __name__ == '__main__': main()
import threading import time # 自定义类,继承threading.Thread class MyThread(threading.Thread): def run(self): for i in range(5): time.sleep(1) # name属性中保存的是当前线程的名字 msg = "I'm " + self.name + ' @ ' + str(i) print(msg) if __name__ == '__main__': # 通过MyThread创建线程对象 t1 = MyThread() # 开始执行线程 t1.start()
注意:
多线程的执行顺序是无序的
多线程共享全局变量
但是由于是多线程同时操作,有可能出现下面情况:
threading模块中定义了Lock类,可以方便的处理锁定:
# 创建锁 mutex = threading.Lock() # 锁定 mutex.acquire() # 释放 mutex.release()
简单实现协程(yield)
import time def work1(): while True: print("----work1---") yield time.sleep(0.5) def work2(): while True: print("----work2---") yield time.sleep(0.5) def main(): w1 = work1() w2 = work2() while True: next(w1) next(w2) if __name__ == "__main__": main()
通过 gevent 实现协程
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) time.sleep(random.random()) gevent.joinall([ gevent.spawn(coroutine_work, "work1"), gevent.spawn(coroutine_work, "work2") ])
进程与线程对比
功能
定义的不同
区别
优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。
协程和线程差异
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU 上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存 Cache 等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作 CPU 的上下文,所以一秒钟切换个上百万次系统都抗的住。
进程、线程和协程的区别
转载地址:http://eqhgx.baihongyu.com/