我们想要了解Python得多线程,就必须要了解GIL,GIL即全局解释锁。
举个栗子
计算机执行程序时,是需要把代码编译成机器指令再去执行的,我们现在用的编辑器,其实就是一种解释器,在我们右键运行程序时,它能够将整个文件编译成字节码,再由Python虚拟机来执行字节码,最后得到输出:
来看一下这个函数的字节码:
Python中有多个线程在同一时间运行同一段代码的时候呢,其实是很容易出错的,所以Python语言在早期的时候为了解决这一问题,便在解释器里加了一个锁,这个锁能够使得在同一时刻只有一个线程在CPU上面去执行这个字节码。也就是说,同一时刻只能有一个线程在一个cpu上面执行字节码。也正因如此,Python在执行多线程任务时,有人会觉得它慢。这样一来,无法显示出多核cpu的优势:
接下来我们看看有没有什么办法能解决这个问题,Python有一个内置的模块threading,它是专门用来解决多线程问题的:
import threading
a=0
def time():
global a #声明全局变量
for item in range(1000000):
a+=1
def test():
global a
for item in range(1000000):
a -= 1
thread_1=threading.Thread(target=time)
thread_2=threading.Thread(target=test)
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
print(a)
按理来说,这个程序的输出结果应该为0,可是:
而且,每次运行时,结果都不一样:
通过上面的运行结果,我们可以看出,整个线程并不是一旦占有就一直占有的!,就好像谈恋爱,有可能会分手一样,你以为能走到最后,可结果…
因此,GIL在某些情况下是可以被释放掉的:
- GIL会根据执行的字节码行数以及时间片进行释放
- 程序会在遇到IO操作的时候 ,会主动释放 GIL
什么是时间片?
比如说time这个线程,会被分配一个时间段来执行,这个时间段即时间片,也就是说,时间结束后,会进入到下一个线程,保证CPU资源不浪费
那么我们怎么进行多线程呢?
先来看看错误的写法:
import time
import threading
def get_data_html():
print('开始获取html数据的时间')
time.sleep(2)
print('获取html数据结束的时间')
def get_data_url():
print('开始获取url数据的时间')
time.sleep(2)
print('获取url数据结束的时间')
if __name__ == '__main__':
thread_1=threading.Thread(target=get_data_html)
thread_2=threading.Thread(target=get_data_html)
start_time = time.time()
thread_1.start()
thread_2.start()
print("中间运行的时间:{}".format(time.time() - start_time))
按理说,程序应该执行2秒,可是并没有,我们来debug一下:
其实这里应该有三个线程,最后一个输出语句是主线程,当主线程退出的时候,子线程会被kill掉了,因此线程没有执行完毕,那么我们可以模块内置的功能去守护线程,让线程继续运行:
if __name__ == '__main__':
thread_1=threading.Thread(target=get_data_html)
thread_2=threading.Thread(target=get_data_url)
thread_1.setDaemon(True) #守护线程
thread_2.setDaemon(True)
start_time = time.time()
thread_1.start()
thread_2.start()
print("中间运行的时间:{}".format(time.time() - start_time))
可是问题又来了:
没有完整地得到结果,我们试着关掉一个线程保护:
if __name__ == '__main__':
thread_1=threading.Thread(target=get_data_html)
thread_2=threading.Thread(target=get_data_url)
# thread_1.setDaemon(True)
thread_2.setDaemon(True)
start_time = time.time()
thread_1.start()
thread_2.start()
print("中间运行的时间:{}".format(time.time() - start_time))
为什么会出现这样的输出?
原因很简单,守护了thread_2,那么thread_2便不会自动结束,它将一直占用CPU,导致thread_1结束时,thread_2还没有结束
下面我们守护thread_1:
发现两个线程都能输出?这里我们改一下time.sleep()的时间,我们让thread_2的时间减少后再试一次:
总的来说,守护线程能避免当主线程退出的时候,子线程会被kill掉的情况
不过,即便如此,这样的方式仍然不是我们想要的,我们希望在两个线程都执行完以后,再来执行主线程
How to do it ?
线程阻塞能解决这个问题:
if __name__ == '__main__':
thread_1=threading.Thread(target=get_data_html)
thread_2=threading.Thread(target=get_data_url)
start_time = time.time()
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
print("中间运行的时间:{}".format(time.time() - start_time))
这里也可以看出,运行的时间并不是两个线程的耗时相加
今天的内容就到这里,下一篇文章将具体介绍多线程编程的应用案例。