-
什么是GIL
全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程。常见的使用 GIL 的解释器有CPython与Ruby MRI。 -- 维基百科
一般用的Python的解释器就是CPython(官网下载的版本),结合上面对GIL的解释,得出两个结论:
-
Python中的多线程并不是同时有多个线程并行执行,而是不断的切换线程达到多线程并行的效果,同一时间只能执行一个线程。
-
Python的多线程程序并不能利用多核CPU的优势,无论有多少个线程,只能运行在一个核上。
-
-
测试GIL
import threading import time from queue import Queue def normal(num_list): total = sum(num_list) print(total) def mutilThread_sum(l, q): q.put(sum(l)) def mutilThread(num_list): q = Queue() threads = [] total = 0 for idx in range(4): thread = threading.Thread(target=mutilThread_sum, args=(num_list[:], q)) thread.start() threads.append(thread) [thread.join() for thread in threads] for thread in threads: total += q.get() print(total) num_list = list(range(1000000)) normal_start = time.time() normal(num_list*4) print('normal:', time.time() - normal_start) mutilThread_start = time.time() mutilThread(num_list) print('mutilThread:', time.time() - mutilThread_start) 复制代码
结果:
单线程计算的是多线程中每个线程计算的4倍,理论上多线程应该比单线程快3~4倍左右,但是从结果看来mutilThread并没有比normal快那么多,而且如果多运行几次你还会发现多线程有时候还会比单线程慢。这就是因为GIL的存在导致的。
-
多进程
虽然无法使用多线程利用多核CPU的优势,但是可以使用多进程达到这一目的。
-
multiprocessing
Python中多进程需要借助multiprocessing模块实现。这个模块和Threading模块很像。
from multiprocessing import Process def add_one(a): print(a + 1) p1 = Process(target=add_one, args=(1,)) # 创建一个进程 p1.start() # 启动进程 p1.join() # 等待进程结束 复制代码
-
进程中函数的输出
观察上面例子中的函数,如果把
print(a + 1)
换为return a + 1
。那么怎样才能拿到这个函数的返回值?用队列。from multiprocessing import Process, Queue def add_one(q, a): q.put(a + 1) q = Queue() p1 = Process(target=add_one, args=(q, 1)) p2 = Process(target=add_one, args=(q, 10)) p1.start() p2.start() p1.join() p2.join() a = q.get() b = q.get() print(a + b) # 13 复制代码
-
-
简单的多进程,多线程效率对比
from multiprocessing import Process, Queue from threading import Thread import time def calculate(q): res = 0 for i in range(10000000): res += i ** 2 q.put(res) # 多进程 def multi_process(): q = Queue() p1 = Process(target=calculate, args=(q,)) p2 = Process(target=calculate, args=(q,)) p1.start() p2.start() p1.join() p2.join() res = q.get() + q.get() print('multi_process:', res) # 多线程 def multi_thread(): q = Queue() p1 = Thread(target=calculate, args=(q,)) p2 = Thread(target=calculate, args=(q,)) p1.start() p2.start() p1.join() p2.join() res = q.get() + q.get() print('multi_thread:', res) # 单线程 def normal(): res = 0 for i in range(2): for j in range(10000000): res += j ** 2 print('normal:', res) t1 = time.time() normal() t2 = time.time() print('normal time:', t2 - t1) multi_process() t3 = time.time() print('multi_process time:', t3 - t2) multi_thread() t4 = time.time() print('multi_thread time:', t4 - t3) 复制代码
结果:
对比一下,多进程效率是最高的,当然了这个例子是属于计算密集型的例子,在io密集型的程序上,多线程的优势还是挺大的。
-
进程池
如果需要大量的创建进程,那么就可以使用进程池这个功能:Pool
from multiprocessing import Pool from os import getpid import time import random def square(num): print('进程id:%s启动' % getpid()) time.sleep(random.random() * 3) print('进程id:%s执行完毕' % getpid()) return num ** 2 p = Pool() results = [] for i in range(10): res = p.apply_async(square, args=(i,)) results.append(res) p.close() p.join() for result in results: print(result.get()) 复制代码
结果:
解释:
-
pool:用于创建一个进程池对象,可以传入同时跑的进程的数量,默认的进程数是cpu的核数。比如:
p = Pool(processes=10) 复制代码
这样就可以让10个进程同时跑,仔细观察上面例子的结果,前8个进程是立即就启动了,因为没有给pool方法传入进程数量,默认就是cpu核的数量。我这里是8,所以同时只能跑8个进程,当这八个进程中的某一个进程的任务执行完成后才会启动新的进程。
-
apply_async:启动进程,执行参数中传入的函数,返回的结果使用get方法获取,注意get是会阻塞的,也就是说如果写成下面这样的话,就变成了一个进程执行完成再启动下一个进程,和单进程没什么两样了:
for i in range(10): res = p.apply_async(square, args=(i,)) res.get() 复制代码
-
close:就是关闭pool,不再接受新的进程。
-
join:等待所有的子进程执行完毕,在调用join之前要先调用close或者terminate方法,terminate方法是结束工作的进程,不管任务是否执行完成。
如果不喜欢apply_async这种,还可以使用map,例子:
from multiprocessing import Pool from os import getpid import time import random def square(num): print('进程id:%s启动' % getpid()) time.sleep(random.random() * 3) print('进程id:%s执行完毕' % getpid()) return num ** 2 p = Pool() res = p.map(square, range(10)) p.close() p.join() print(res) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 复制代码
-
转载于:https://juejin.im/post/5c455bf151882524ff640ca4