首页 > 代码库 > 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 线程队列)