【Python爬虫学习笔记11】Queue线程安全队列和GIL全局解释器锁
Queue线程安全队列
在Python多线程编程中,虽然threading模块为我们提供了Lock类和Condition类借助锁机制来处理线程并发执行,但在实际开发中使用加锁和释放锁仍是一个经常性的且较为繁琐的过程。因此,Python中又为我们提供了一个使用起来更为简单的模块——queue模块。
queue模块是一个线程安全的模块(线程安全——即不存在共享变量访问冲突问题),该模块提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue以及LIFO(后进先出)队列LifoQueue。这些队列都已经实现了锁的原子操作(即要么不做,要么都做完),可以方便地在多线程中直接使用,实现线程间的同步。
其相关函数如下:
1.Queue(maxsize)/LifoQueue(maxsize):创建并初始化一个大小为maxsize的先进先出/后进先出的队列。
2.qsize():返回队列当前以存放数据的大小。
3.empty():判断队列是否为空。
4.full():判断队列是否满了。
5.get():从队列中取一个队首数据。
6.put(data):将一个数据放到队列中。
具体的使用示例如下:
##queue模块的简单使用示例 from queue import Queue # 初始化队列 q = Queue(7) # 判断队列是否为空 print(q.empty()) #True # 存放数据 for i in range(5): q.put(i) # 输出当前队列大小 print(q.qsize()) #5 # 判断队列是否为满 print(q.full()) #False # 依次取出队列元素并以列表形式输出 print([q.get() for x in range(q.qsize())]) #[0,1,2,3,4] print(q.empty()) #True
其实在get()方法中,还可以设置一个参数block,其代表当队列中没有数据时应采取的操作,默认值为True表示持续等待,如果设置为Flase则当没有数据时抛出Empty异常。
## 设置get()方法参数block from queue import Queue,Empty q = Queue(3) for i in range(2): q.put(i) elements = [q.get() for x in range(q.qsize())] try: x = q.get(block=False) except Empty: print('The queue is empty.') """ Output:The queue is empty. """
GIL全局解释器锁
首先,我们先来了解一下Python自带的解释器——CPython
。CPython
解释器是基于C语言编写的解释器,其的多线程机制并不是一个正真的多线程,在多核CPU中,Cpython只能利用一核而不能利用多核,即同一时刻只有一个线程在执行,而表现出多线程是因为一个CPU可以在极短的时间内轮转执行每一个线程。
为了保证同一时刻只有一个线程在执行,在CPython
解释器中有一个叫做全局解释器锁GIL(Global Intepreter Lock)
的东西,其主要是用于解决CPython
解释器内存管理非线程安全的问题。
而除了CPython
解释器外,还有如下其他的解释器:
1.Jython:基于Java实现的Python解释器,不存在GIL锁。维基百科:https://en.wikipedia.org/wiki/Jython
2.IronPython:基于.net实现的Python解释器,不存在GIL锁。维基百科:https://en.wikipedia.org/wiki/IronPython
3.PyPy:基于Python实现的Python解释器,存在GIL锁。维基百科:https://en.wikipedia.org/wiki/PyPy
GIL虽然是真正意义上的多线程,但在处理一些IO操作(如文件读写和网络请求等)时还是可以提高很多效率的,因此在IO操作上更建议使用多线程机制;然而在一些CPU计算操作上用多线程并发可能反而会使效率低下,因此在这种情况下更建议使用多进程处理。