Python中的多进程、多线程和协程

本文中的内容来自我的笔记。撰写过程中参考了胡俊峰老师《Python程序设计与数据科学导论》课程的内容。

并发处理:多进程和多线程 前置

概念:

并发:一段时间内同时推进多个任务,但不一定要在一个时刻同时进行多个任务。

并行:一段时间内同时推进多个任务,且在一个时刻要同时进行多个任务。

并行是并发的子集;单核CPU交替执行多个任务是并发但不是并行;多核CPU同时执行多个任务既是并发也是并行。

何时需要并发?

需要同时处理多个任务

经常需要等待资源

多个子过程互相协作

电脑执行任务的机制:

操作系统内核 负责任务(i.e. 进程/线程)的挂起与唤醒,和资源的分配(比如一个程序能访问哪些内存地址)

进程是资源分配的最小单元,不同进程之间不共享资源(比如可访问的内存区域);进程可再分为线程,线程之间共享大部分资源。

正是因为 是否共享资源 上的区别,线程间的切换(即挂起和唤醒)比进程间的切换更快。

线程是调度执行的最小单元。这意味着操作系统内核会负责将多个线程并发执行。

多进程和多线程的比较

多进程:

将任务拆分为多个进程来进行

由内核决定是并行还是仅仅并发。

进程间不共享内存

优点:一个进程挂了不影响别的

缺点:切换进程耗时大、进程间通信不便

多线程:

将任务拆分为一个进程内的多个线程来进行

由内核决定是并行还是仅仅并发。

在CPython解释器中有全局解释器锁,导致多线程只能并发而不能并行(多进程可以并行)。

进程间共享内存

优点:切换耗时低、通信方便

缺点:在并行时对全局变量要使用锁机制

锁机制:一个线程使用一个全局变量时,先等待其(被其他线程)解锁,再将其上锁,再使用,用后再解锁。

如果不使用锁的话:100个a+=1的线程执行完成后(初始a=0),a可能<100 。

数据科学中可以为了提高效率而不使用锁机制,但同时要容忍由此带来的差错。

多进程的机制和代码实现

以下介绍的函数中,几乎每一个有阻塞可能的,都会有一个可选的timeout参数。这件事将不再重提。

基本用法 from multiprocessing import Process import os import time def task(duration, base_time): pid = os.getpid() print(f'son process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s with parameter {duration}') time.sleep(duration) print(f'son process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s') if __name__ == '__main__': pid = os.getpid() base_time = time.perf_counter() print(f'main process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s') p1 = Process(target=task, args=(1,base_time)) # a process that executes task(1,base_time); currently not running p2 = Process(target=task, args=(2,base_time)) # a process that executes task(2,base_time); currently not running p1.start() p2.start() print(p1.is_alive(), p2.is_alive()) # whether they are running print('main process can proceed while son processes are running') p1.join() # wait until p1 finishes executing (the main process will pause on this command in the meantime) and kill it after it finishes p2.join() # wait until p2 finishes executing (the main process will pause on this command in the meantime) and kill it after it finishes print(f'main process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s') main process id 3316 starts at 0.000001s True True main process can proceed while son processes are running son process id 15640 starts at 0.002056s with parameter 1 son process id 10716 starts at 0.003030s with parameter 2 son process id 15640 ends at 1.002352s son process id 10716 ends at 2.017861s main process id 3316 ends at 2.114324s

如果没有p1.join()和p2.join(),主进程会在很短的时间内完成执行,而此时子进程仍在运行。输出结果如下:

main process id 11564 starts at 0.000001s True True main process can proceed while son processes are running main process id 11564 ends at 0.011759s son process id 13500 starts at 0.004392s with parameter 1 son process id 11624 starts at 0.003182s with parameter 2 son process id 13500 ends at 1.009420s son process id 11624 ends at 2.021817s

为何0.004秒的事件在0.003秒的事件之前被输出?

因为print语句的耗时未被计算在内

因为perf_counter()在Windows下有bug,它给出的时间在不同进程之间不完全同步

需要注意,一个子进程结束运行后仍然处于存活状态;只有被join()之后才会正式死亡(即被从系统中除名)。

关于if __name__ == '__main__'::

在Python中,可以通过import来获取其他文件中的代码;在文件B的开头(其他位置同理)import文件A,相当于把A在B的开头复制一份。

如果在复制A的内容时,我们希望A中的一部分代码在执行时被忽略(比如说测试语句),就可以给A中的这些代码加上if __name__ == '__main__':

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgdgd.html