11.python并发入门(part7 线程队列)

1、为何要用队列?
python

队列是一种数据结构,数据结构是一种存放数据的容器,和列表,元祖,字典同样,这些都属于数据结构。
安全

队列能够作的事情,列表均可以作,可是为何咱们还要去使用队列呢?数据结构

这是由于在多线程的状况下,列表是一种不安全的数据结构。多线程

为何不安全?能够看下面这个例子:并发

#开启两个线程,这两个线程并发从列表中移除一个元素。python2.7

import threadingide

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函数的开头部分开始从新执行......一直循环下去直到列表为空。



其实上面出现的这种状况,咱们能够经过互斥锁或者递归锁的方式去解决。

若是不想加锁,咱们就可使用队列这种数据类型。


2、队列的基本使用。

首先介绍下线程队列的基本用法。

一、要使用线程队列以前,首先须要导入一个名为Queue的模块。

import Queue


二、初始化一个线程队列的对象,这个队列的长度能够无限,也能够有限,这个队列的大小能够经过maxsize去指定。

q1 = Queue.Queue(maxsize=10)


三、将一个值放入队列中。

q1.put(“a”)

调用队列对象的put方法,实现的是在队列的尾部插入一个数据,put方法有两个参数,第一个是item,也就是要在队列尾部插入的数据(这个是必填的),另一个就是block参数,这个block参数若是不填,默认为1。

若是当一个队列为空,而且block为1时,put方法就会使调用put方法的这个线程挂起,直到有空的数据单元,若是block设置为0,一旦队列满了,程序就会抛出一个full异常。


四、从队列中取出一个值。

q1.get()

调用队列的get方法,从队列的头部删除而且返回一个元素,get方法也有一个可选参数,也叫block,默认为True。

当队列为空,block为“True”时,调用get方法的那个线程会被挂起(保存状态暂停),一直等到对了里面有数据为止。

当队列为空,block为“False”时,就会直接抛出Empty异常。


五、队列的三种模式。

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()

相关文章
相关标签/搜索