一、进程与线程
1.进程
操作系统进行资源分配和调度的基本单位,多个进程之间相互独立。
2.线程
CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享进程的多个资源。
二、GIL锁
1、GIL定义
全局解释器锁,简单来说是一个互斥锁,每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
2、GIL的准则
1)当前执行线程必须持有GIL
2)当线程遇到 IO的时、时间片到时或遇到阻塞时, 会释放GIL(Python 3.x使用计时器----执行时间达到阈值后,当前线程释放GIL,或Python 2.x,tickets计数达到100。)
三、多线层不能充分利用多核GPU的优势
1、CPU密集采用多进程,假如IO操作少,用多线程的话,因为线程共享一个全局解释器锁,当前运行的线程会霸占GIL,其他线程没有GIL,就不能充分利用多核CPU的优势。
计算密集型--采用多进程(耗时2秒左右)
from multiprocessing import Process
import time
from threading import Thread
def foo():
res = 1.1
for i in range(1, 100000000):
res *= i
if __name__ == '__main__':
l = []
start_time = time.time()
for i in range(8):
p = Process(target=foo)
p.start() # 启动进程,并调用该子进程中的p.run(),p.run()调用target指定的函数
l.append(p)
for p in l:
p.join() # 主进程等待所有子进程p执行完毕,才执行
print(time.time() - start_time)
计算密集型--采用多线程(耗时37秒)
from multiprocessing import Process
import time
from threading import Thread
def foo():
res = 1.1
for i in range(1, 100000000):
res *= i
if __name__ == '__main__':
l = []
start_time = time.time()
for i in range(8):
p = Thread(target=foo)
p.start()
l.append(p)
for p in l:
p.join()
print(time.time() - start_time)
2、IO操作密集采用多线程,在用户输入,sleep的时候,可以切换到其他线程执行,减少等待时间。常见IO密集型任务:网络IO(抓取网页数据)、磁盘操作(读写文件)、键盘输入。
IO密集型--采用多进程(耗时19秒)
def foo():
time.sleep(2) # IO操作时会释放CPU -- CPU大多数时间是处于闲置状态,频繁的切换
if __name__ == '__main__':
l = []
start_time = time.time()
for i in range(1000):
p = Process(target=foo)
p.start()
l.append(p)
for p in l:
p.join()
print(time.time() - start_time)
IO密集型--采用多线程(耗时2秒)
def foo():
time.sleep(2)
if __name__ == '__main__':
l = []
start_time = time.time()
for i in range(1000):
p = Thread(target=foo)
p.start()
l.append(p)
for p in l:
p.join()
print(time.time() - start_time)
四、总结
1、对于IO密集型应用,即使有GIL存在,由于IO操作会导致GIL释放,其他线程能够获得执行权限。由于多线程的通讯成本低于多进程,因此偏向于使用多线程。
2、对于计算密集型应用,由于CPU一直处于被占用,GIL锁直到规定时间才会释放,然后才会切换状态,导致多线程处于绝对的劣势,此时可以采用多进程+协程。