首页 > 代码库 > 11.python并发入门(part7 线程队列)
11.python并发入门(part7 线程队列)
一、为什么要用队列?
队列是一种数据结构,数据结构是一种存放数据的容器,和列表,元祖,字典一样,这些都属于数据结构。
队列可以做的事情,列表都可以做,但是为什么我们还要去使用队列呢?
这是因为在多线程的情况下,列表是一种不安全的数据结构。
为什么不安全?可以看下面这个例子:
#开启两个线程,这两个线程并发从列表中移除一个元素。
import threading
import time
l1 = [1,2,3,4,5]
def pri():
while l1:
a = l1[-1]
print a
time.sleep(1)
try:
l1.remove(a)
except Exception as e:
print "%s-------%s" %(a,e)
t1 = threading.Thread(target=pri)
t1.start()
t2 = threading.Thread(target=pri)
t2.start()
输出结果:
5
5
4
5-------list.remove(x): x not in list
4
34-------list.remove(x): x not in list
3
23-------list.remove(x): x not in list
2
1
2-------list.remove(x): x not in list
1
1-------list.remove(x): x not in list
关于上述的代码分析:
主线程首先创建了两个线程t1和t2 并启动。
首先t1执行pri函数,首先获取了列表中最后一个元素(5),并且print输出,sleep1秒,此时切换到t2线程,t2线程执行pri函数,同时也获取到了列表中的最后一个元素(5),print输出,然后sleep1秒,此时切换回t1线程,t1线程执行l1.remove删除刚刚获取到的列表中的最后一个元素(5),成功删除,没有出现任何异常。
此时,列表中只剩下了[1,2,3,4]四个元素,t1线程重新再去执行pri函数,首先获取列表中的最后一个元素(此时的最后一个元素是4),并且print输出最后一个元素(4),print完毕之后sleep1秒,此时又会切换到t2线程,刚刚t2线程拿到的最后一个元素是5,刚刚t2线程拿到了元素5之后执行sleep 1后就阻塞了,现在t2线程开始执行sleep 1后面的代码,l1.remove()现在t2线程要去删除的是之前t2线程获取到的最后一个元素5,但是元素5之前已经被t1线程从列表中删掉了,所以现在元素5是不存在的,就会抛出一个异常,except下面的代码块就会运行,输出一个“5-------list.remove(x): x not in list” 因为之前捕捉了异常,所以程序不会崩溃,然后继续执行~
t2线程print一个错误信息“5-------list.remove(x): x not in list”后,回到pri函数的开头,继续重新执行,重新去获得l1列表中的最后一个元素(4),然后print输出(4),再然后sleep1秒,切换到t1线程。
t1线程刚刚拿到的最后一个元素是(4)刚刚执行到了pri函数的sleep1,结束了sleep之后,开始执行list.remove移除列表中的最后一个元素,此时元素4从l1列表中被删除,现在的l1列表中只剩下了[1,2,3]三个元素。
t1线程继续回到pri函数开始的位置,获取列表中的最后一个元素后(3)并print输出,然后sleep,此时切换到t2线程,t2线程刚才拿到的最后一个元素是4,显然元素4已经被刚刚的t1线程给移除掉了,当t2去移除元素4时,又会出现异常,print输出“4-------list.remove(x): x not in list”,然后t2线程继续回到pri函数的开头部分开始重新执行......一直循环下去直到列表为空。
其实上面出现的这种情况,我们可以通过互斥锁或者递归锁的方式去解决。
如果不想加锁,我们就可以使用队列这种数据类型。
二、队列的基本使用。
首先介绍下线程队列的基本用法。
1、要使用线程队列之前,首先需要导入一个名为Queue的模块。
import Queue
2、初始化一个线程队列的对象,这个队列的长度可以无限,也可以有限,这个队列的大小可以通过maxsize去指定。
q1 = Queue.Queue(maxsize=10)
3、将一个值放入队列中。
q1.put(“a”)
调用队列对象的put方法,实现的是在队列的尾部插入一个数据,put方法有两个参数,第一个是item,也就是要在队列尾部插入的数据(这个是必填的),另外一个就是block参数,这个block参数如果不填,默认为1。
如果当一个队列为空,并且block为1时,put方法就会使调用put方法的这个线程挂起,直到有空的数据单元,如果block设置为0,一旦队列满了,程序就会抛出一个full异常。
4、从队列中取出一个值。
q1.get()
调用队列的get方法,从队列的头部删除并且返回一个元素,get方法也有一个可选参数,也叫block,默认为True。
当队列为空,block为“True”时,调用get方法的那个线程会被挂起(保存状态暂停),一直等到对了里面有数据为止。
当队列为空,block为“False”时,就会直接抛出Empty异常。
5、队列的三种模式。
Queue.Queue :FIFO 先进先出。
Queue.LifoQueue:LIFO 后进先出(先进后出)
Queue.PriorityQueue:这是一种优先级队列,给每个队列中的元素指定一个优先级,优先级越低的越先从队列中出去。
6.关于队列中可能会常用的其他方法。
q1.qsize() 获取当前队列的大小。
q1.empty() 如果队列为空时,返回True,否则返回False
q1.full() 如果队列满了返回True,否则返回False。
q1.get([block[, timeout]]) 获取队列中的数据,timeout为等待时间。
q1.get_nowait() 相当q1.get(block = False)
q1.put(item) 在队列中添加数据,timeout为等待时间。
q1.put_nowait(item) 相当q1.put(item, block=False)。
q1.task_done() 在完成一项工作之后,q1.task_done() 函数向任务已经完成的队列发送一个信号。
q1.join() 实际上意味着等到队列为空,再执行别的操作.
在多线程中使用队列这种数据类型去代替列表后:
#!/usr/local/bin/python2.7
# -*- coding:utf-8 -*-
import Queue
import threading
import time
q1 = Queue.PriorityQueue(maxsize=10)
for i in xrange(1,6):
q1.put(i)
def pri():
while not q1.empty():
a = q1.get()
print a
time.sleep(1)
t1 = threading.Thread(target=pri)
t1.start()
t2 = threading.Thread(target=pri)
t2.start()
本文出自 “reBiRTH” 博客,请务必保留此出处http://suhaozhi.blog.51cto.com/7272298/1925477
11.python并发入门(part7 线程队列)