本系列是学习 廖雪峰 Python3 教程 过程中记录的笔记,本篇文章记录 Python 中的进程和线程的相关内容,主要包括多进程、多线程的实现,ThreadLocal 的使用,以及对异步 I/O 和 分布式进程的介绍。
进程和线程
- 线程是最小的执行单元,而进程由至少一个线程组成。
- 如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
- 多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。
多进程
-
在
Unix/Linux/Mac下,可以使用fork()调用实现多进程,multiprocessing模块可实现跨平台的多进程; -
启动几个子进程,使用
Process模块Process(target=func, args=(,)); -
启动大量子进程,用进程池
Pool批量创建,chi.apply_async(func=func, args=(,); -
进程间通信是通过
Queue、Pipes等实现的;"""Process()创建子进程 记录子进程启动的时间戳""" import time from multiprocessing import Process def action(name): # 待会两个进程要执行的任务↓ content = name for i in range(3): # 循环3次 content += str(time.time()) + ' -> ' time.sleep(0.2) # 等待0.2s print(content) if __name__ == '__main__': # 这行代码很重要,新建进程的时候都加上它!! jc1 = Process(target=action, args=('进程一 时间戳:',)) # 准备建立一个进程:multiprocessing.Process() jc2 = Process(target=action, args=('进程二 时间戳:',)) # 再准备建立一个新进程,这是基本格式记住← jc1.start() # 将蓄势待发的jc1进程正式启动!! jc2.start() # 同上... jc1.join() # 等待进程jc1将任务执行完... jc2.join() # ... print('来自主进程:jc1,jc2任务都已执行完毕') jc1.close() # 彻底关闭进程jc1 jc2.close() # ... output—————————————————————— 进程一 时间戳:1567329729.133326 -> 1567329729.3337915 -> 1567329729.534282 -> 进程二 时间戳:1567329729.1503086 -> 1567329729.3507462 -> 1567329729.5512364 -> 来自主进程:jc1,jc2任务都已执行完毕"""Pool() 创建多个子进程,记录每次调用时分配的进程id""" import time, os from multiprocessing import Pool def action1(name, b=3): content = name for i in range(b): content += str(i) + " pid: " + str(os.getpid()) time.sleep(0.1) print(content) if __name__ == '__main__': # 还要添加这行,否则可能出现异常 chi = Pool(2) # 同时执行的最大进程数,并非进程池的容量 chi.apply_async(func=action1, args=('进程一',)) # 启动第一个子进程... chi.apply_async(func=action1, args=('进程二', 3)) # 和普通进程的启动方式有很大不同仔细看 chi.apply_async(func=action1, args=('进程三', 4)) # Pool的最基本格式记住← # 注意:程序现在有4个进程在运行:上面的三个子进程 和一个最为核心的:主进程 chi.close() # 关闭进程池(但池子内已启动的子进程还会继续进行) chi.join() # 等待进程池内的所有子进程完毕 print('子进程全部结束,主进程在此!') output—————————————————————————— 进程一0 pid: 166801 pid: 166802 pid: 16680 进程二0 pid: 161161 pid: 161162 pid: 16116 进程三0 pid: 166801 pid: 166802 pid: 166803 pid: 16680 子进程全部结束,主进程在此!"""使用Queue() 完成子进程和主进程之间的通信""" from multiprocessing import Queue, Process def foo(aa): mes = aa.get() # 管子的另一端放在子进程这里,等待接收数据,【阻塞】 print('子进程——', mes) aa.put("收到!已经中止。") if __name__ == '__main__': # 要加这行... tx = Queue() # 创建进程通信的Queue,你可以理解为我拿了个管子来... jc = Process(target=foo, args=(tx,)) # 创建子进程 jc.start() # 启子子进程 print('主进程准备发送数据...') tx.put('有内鬼,终止交易!') # 将管子的一端放在主进程这里,主进程往管子里丢入数据↑ jc.join() # 等待子进程中止 message = tx.get() print('主进程——', message) output—————————————————————————— 主进程准备发送数据... 子进程—— 有内鬼,终止交易! 主进程—— 收到!已经中止。
多线程
-
使用
threading.Thread(target=run_thread, args=(,))来创建新线程; -
使用
threading.Lock()来创建锁,操作共享变量前申请获得、用完后一定要释放; -
多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生;
-
Python 解释器由于设计时有 GIL 全局锁,导致了多线程无法利用多核。多线程的并发在 Python 中就是一个美丽的梦。
import threading balance = 0 def change_it(n): # 先存后取,结果应该为0 global balance balance = balance + n balance = balance - n def run_thread(n): lock = threading.Lock() for i in range(100000): lock.acquire() # 先要获取锁 try: change_it(n) # 放心地改吧 finally: lock.release() # 改完了一定要释放锁 def main(): t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print('total balance is :', balance) if __name__ == '__main__': main() output—————————————————————————— total balance is : 0
ThreadLocal
-
调用
threading.local()实现,解决了参数在一个线程中各个函数之间互相传递的问题; -
一个
ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰;import threading # 创建全局ThreadLocal对象: local_school = threading.local() def process_student(): # 获取当前线程关联的student: std = local_school.student print('Hello, %s (in %s)' % (std, threading.current_thread().name)) def process_thread(name): # 绑定ThreadLocal的student: local_school.student = name process_student() t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A') t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-B') t1.start() t2.start() t1.join() t2.join() output—————————————————————— Hello, Alice (in Thread-A) Hello, Bob (in Thread-B)
异步 I/O 和 分布式进程
- 单线程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序;
- 事件驱动模型:充分利用操作系统提供的异步IO支持,用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型;
- 对于进程和线程,优先选择
Process,更稳定且可以部署到多台机器,Thread最多只能分布到同一台机器的多个CPU上; Python的分布式进程接口简单,封装良好,适合需要把繁重任务分布到多台机器的环境下;- 注意
Queue的作用是用来传递任务和接收结果,每个任务的描述数据量要尽量小,减轻通信的负担; - 分布式进程详见 这里 ,另外,分布式进程之间怎么协作呢?


被折叠的 条评论
为什么被折叠?



