由于Python中全局解释器锁(GIL)的存在,在任意时刻只允许一个线程在解释器中运行,因此Python的多线程不适合处理CPU密集型的任务。
要求:想要处理CPU密集型的任务,可以使用多进程模型。
解决方案:
使用标准库中multiprocessing.Process
类,它可以确定子进程执行任务。操作接口、进程间通信、进程加同步等都与threading.Thread
类类似。
- 对于
multiprocessing.Process
类:
在multiprocessing中,通过创建一个Process对象然后调用它的start()
方法来生成进程。 Process和threading.Thread
的API 相同,start()
、join()
和run()
方法作用一致。
进程和线程的根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
进程是线程的容器,不存在没有线程的进程。如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据。
- 对于进程间通信:
队列:
线程间通信使用的是标准库中的queue.Queue
类,它是一个线程安全的队列。而进程间通信使用的是multiprocessing.Queue
类,它是一个近似queue.Queue
的克隆,也是线程和进程安全的。
管道:
Pipe()
函数返回一个由管道连接的连接对象,默认情况下是双工(双向)。返回的两个连接对象Pipe()
表示管道的两端。每个连接对象都有send()
和recv()
方法(相互之间的)。请注意,如果两个进程(或线程)同时尝试读取或写入管道的同一端,则管道中的数据可能会损坏。当然,同时使用管道的不同端的进程不存在损坏的风险。
>>> from multiprocessing import Process, Pipe>>> c1, c2 = Pipe()>>> def f(c):... print('in child')... data = c.recv()... print(data)... c.send(data * 2)... >>> Process(target=f, args=(c2,)).start()in child>>> c1.send(100)100>>> c1.recv()200
send()
方法表示输入数据到管道;recv()
方法表示从管道中接收数据。
- 方案示例:
import timefrom threading import Threadfrom multiprocessing import Processfrom queue import Queue as Thread_Queuefrom multiprocessing import Queue as Process_Queuedef is_armstrong(n): #判断是否是水仙花数 a, t = [], n while t : a.append(t % 10) t //= 10 k = len(a) return sum(x**k for x in a) == ndef find_armstrong(a, b, q=None): #在(a, b)内找出水仙花数 res = [x for x in range(a, b) if is_armstrong(x)] if q: q.put(res) return resdef find_by_thread(*ranges): #通过线程寻找 q = Thread_Queue() workers = [] for r in ranges: a, b = r t = Thread(target=find_armstrong, args=(a, b, q)) t.start() workers.append(t) res = [] for _ in range(len(ranges)): res.append(q.get()) return resdef find_by_process(*ranges): #通过进程寻找 q = Process_Queue() workers = [] for r in ranges: a, b = r t = Process(target=find_armstrong, args=(a, b, q)) t.start() workers.append(t) res = [] for _ in range(len(ranges)): res.extend(q.get()) return resif __name__ == '__main__': t0 = time.time() res = find_by_thread([10000000, 15000000], [15000000, 20000000], [20000000, 25000000], [25000000, 30000000]) # res = find_by_process([10000000, 15000000], [15000000, 20000000], [20000000, 25000000], [25000000, 30000000]) print(res) print(time.time() - t0)
[[24678050, 24678051], [], [], []] #find_by_thread 结果 98.16692352294922 [24678050, 24678051] #find_by_process 结果 55.84143948554993
从运行结果来看,对于CPU密集型的任务,通过多进程来处理比多线程更好。