随着计算机技术的发展,越来越多的应用程序需要并行处理来提高计算性能。Python作为一种高级编程语言,拥有强大的并行机制,可以轻松实现多线程、多进程和分布式计算。相比于其它的编程语言,Python能够让开发者用更少的代码完成更复杂的并行工作。
1、进程与线程介绍
(1)进程的基本概念
进程是电脑运行时资源分配的最小单位,也是每个程序相互隔离的边界。每次我们执行一个程序或打开一个软件时, 我们的操作系统会自动的为这个程序准备一些必要的资源,包括分配内存, 创建一个能够执行的线程等。
想要查看Windows系统的进程,我们可以同时按住Ctrl+Alt+Delete键,点击打开任务管理器,就可以通过任务管理器来查看本机进程,这里罗列了所有正在运行的程序和服务,以及它们占用的系统资源。如下图:
在任务管理器里面,如果想要关闭某个进程,可以选中该进程并右击鼠标选择“结束任务”按钮,就可以直接关闭该程序所有的进程。
众所周知,所有的电脑都支持同时运行多个任务,每个任务通常是一个程序,每一个运行中的程序就是一个进程,例如,程序员一边打开Pyhint编辑器编写程序就是启动一个编辑进程,一边打开浏览器参考网上代码就是启动一个浏览器进程,同时还使用电脑打开播放器播放音乐就是启动一个播放进程,除此之外,每台电脑运行时还有大量底层的支撑性程序在运行,比如进行打字、拼写检查、打印等进程,这些进程看上去像是在同时工作。但实际上,对于单核CPU而言,在某个时间点它只能执行一个程序。也就是说,只能运行一个进程,CPU不断地在这些进程之间轮换执行。比如,进程1执行0.01秒,切换到进程2,进程2执行0.01秒,再切换到进程3,执行0.01秒……这样反复执行下去。那么,为什么用户感觉不到任何中断呢?
这是因为相当于肉眼来说,CPU的执行速度非常快,我们感觉不到进程之间轮换。假如启动的程序数量足够多,则我们依然可以感觉到程序的运行速度下降了。所以,虽然CPU在多个进程之间轮换执行,但用户感觉到好像有多个进程在同时执行。
如今,多核CPU已经非常普及了,真正的并行执行多进程只能在多核CPU的电脑上实现,但是,由于进程数量远远多于CPU的核心数量,所以,操作系统也会自动把多个进程轮流调度到每个CPU核心上执行。
在前面的教程中,我们编写的所有Python程序,都是执行单任务的进程,每个进程都有自己的内存空间、数据栈和程序计数器,它们相互独立地运行,并且在操作系统中拥有自己的标识符和状态。每个进程可以被看作是操作系统进行资源分配和调度的基本单位,它们可以并发地执行,从而实现多任务处理。并且每个进程可以派生子进程,每个进程之间还可以相互通信。
(2)线程的基本概念
在很久以前,计算机编程并没有线程这个概念,但是随着时代的发展,只用进程来处理程序会出现许多的不足。例如,我们在编写一个Python程序时,当一个进程突然出现堵塞时,整个程序会停止在堵塞处,并且如果我们频繁地切换或重启进程,会浪费大量时间和系统资源,所以后来就出现了线程这个概念。一个进程可以包含一个或者多个线程,当其中一个线程出现堵塞时,不会影响其他线程的运行。
线程是进程的组成部分,一个进程可以拥有多个线程,这些线程共享此进程的内存空间与资源(如代码段,数据集,堆栈等)。进程相当于把一个任务又细分成若干个子任务,每个线程对应一个子任务。当一个进程里只有一个线程时,叫作单线程,如果一个进程里有多个线程就叫作多线程。在多线程中,会有一个主线程来完成整个进程从开始到结束的全部操作,而其他的线程会在主线程的运行过程中被创建或退出。
每个线程必须有一个父进程,线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有新的系统资源,因为它与父进程的其他线程共享该进程所拥有的全部系统资源。
每个线程是独立运行的,它并不知道进程中是否还有其他线程存在。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发运行。
总之,我们的电脑可以同时执行多个任务,每一个任务就是一个进程,每个进程又可以把任务细分成若干个子任务,每一个子任务就是一个线程。
2、Python中的全局解释器锁
全局解释器锁(Global Interpreter Lock,简称 GIL),是Python中的一个概念,它确保一个线程可以在全局解释器中执行代码,从而防止多线程环境下的线程真正并行执行。这是因为在Python中,虽然有多线程的机制,但是在解释器级别,任何时候只有一个线程可以执行操作。这是为了保护解释器内部的数据结构不会被并发访问引起的数据不一致问题。因为线程在某个进程中是共享内存的,所以线程间的数据是可以互相访问的。但是,当多个线程同时修改数据时就会出现问题。
GIL的存在主要是为了简化线程的管理,当某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个Python进程中,GIL只有一个。拿不到GIL通行证的线程,就不允许进入CPU执行。Python中,无论是单核CPU还是多核CPU,同时只能由一个线程在执行。其根源是GIL的存在。
在多线程中,每次释放GIL锁,进行切换线程,会消耗资源。这就导致打印线程执行时间长的原因。
并且由于GIL锁存在,Python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,Python的多线程效率并不高的根本原因。
在Python中,虽然GIL的存在限制了多线程的并行能力,但是对于IO密集的应用(IO密集的应用是指在执行过程中主要依赖于IO操作,如网络请求、文件读写等),多线程还是可以提高程序的效率。对于CPU密集的应用,可以考虑使用多进程来提高效率,因为每个进程拥有自己的独立的GIL,可以并行执行。
每个Python程序至少有一个线程,这就是主线程,程序在启动后由Python解释器负责创建主线程,在程序结束后由Python解释器负责停止主线程。
这里需要注意的是,对于复杂的计算密集型任务,多线程通常仍然不会比单线程更高效,因为大部分时间线程是在等待GIL释放。