编写高质量的python代码(4): 使用Queue使多线程编程更安全

      曾经有这么一个说法,程序中存在3种类型的bug:你的bug,我的bug和多线程。这虽然是句调侃,但从某种程度上道出了一个事实:多线程编程不是件容易的事情。线程间的同步和互斥,线程间数据的共享等这些都是涉及线程安全方面要考虑的问题。纵然python中提供了众多的同步和互斥机制,如mutex,condition,event等,但同步和互斥本身就不是一个容易的话题,稍有不慎就会陷入死锁状态或者威胁线程安全。我们来看一个经典的多线程同步问题:生产者消费者模型。如果用python来实现,你会怎么写?大概思路是这样:分别创建消费者和生产者线程,生产者往队列里面放产品,消费者从队列里面取出产品,创建一个线程锁以保证线程间操作的互斥性。当队列满足的时候消费者进入等待状态,当队列为空的时候生产者进入等待状态。我们来看一个具体的python实现:

# -*- coding: utf-8 -*-
import Queue
import threading
import random


writelock = threading.Lock()

class Producer(threading.Thread):
    def __init__(self, q, con, name):
        super(Producer, self).__init__()
        self.q = q
        self.name = name
        self.con = con
        print "Producer " + self.name + " Started"
    def run(self):
        while 1:
            global writelock
            self.con.acquire()   # 获取锁对象
            if self.q.full():
                with writelock:
                    print 'Queue is full, producer wait!'
                self.con.wait()  # 等待资源
            else:
                value = random.randint(0, 10)
                with writelock:
                    print self.name + " put value" + self.name + ":" + str(value)+ "into queue"
            self.q.put((self.name + ":" + str(value)))
            self.con.notify()
        self.con.release()


class Consumer(threading.Thread):
    def __init__(self, q, con, name):
        super(Consumer, self).__init__()
        self.q = q
        self.con = con
        self.name = name
        print "Consumer "+self.name+" started\n"

    def run(self):
        while 1:
            global writelock
            self.con.acquire()
            if self.q.empty():
                with writelock:
                    print 'queue is empty, consumer wait!'
                self.con.wait()
            else:
                value = self.q.get()
                with writelock:
                    print self.name + "get value" + value + " from queue"
                self.con.notify()
            self.con.release()

if __name__ == "__main__":
    q = Queue.Queue(10)
    con = threading.Condition()
    p = Producer(q, con, "P1")
    p.start()
    p1 = Producer(q, con, "P2")
    p1.start()
    c1 = Consumer(q, con, "C1")
    c1.start()

      上面的程序实现有什么问题吗?回答这个问题之前,我们先来了解一下Queue模块的基本知识。Python中的Queue模块提供了3种队列:

  • Queue.Queue(maxsize): 先进先出,maxsize为队列大小,其值为非正数非时候为无限循环队列
  • Queue.LifoQueue(maxsize): 后进先出,相当于栈
  • Queue.PriorityQueue(maxsize): 优先级队列。
    这三种队列支持以下方法:
  • Queue.qsize(): 返回近似的队列大小。注意,这里之所以加“近似”二字,是因为该值>0的时候并不保证并发执行的时候get()方法不被阻塞,同样,对于put()方法有效
  • Queue.empty(): 队列为空的时候返回True, 否则返回False
  • Queue.full(): 当设定了队列大小的时候,如果队列满则返回True,否则返回False
  • Queue.put(item[,block[, timeout]]): 往队列里添加元素item,block设置为False的时候,如果对列满则抛出Full异常。如果block设置为True,timeout为None的时候则会一直等待到有空位置,否则会根据timeout的设定超时后抛出full异常
  • Queue.put_nowait(item): 等价于put(item, False).block设置为False的时候,如果对列空咋抛出Empty异常。如果block设置为True,timeout为None的时候则会一直等待到有元素可用,否则会根据timeout的设定超时后抛出empty异常
  • Queue.get([block[,timeout]]): 从队列中删除元素并返回该元素的值
  • Queue.get_nowait(): 等价于get(False)
  • Queue.task_done(): 发送信号表明入队任务已经完成,经常在消费者线程中用到
  • Queue.join(): 阻塞直至对列中所有元素处理完毕
          Queue模块实现了多个生产者消费者的对列,当线程之间需要信息安全的交换的时候特别的有用,因此这个模块实现了所需要的锁原语,为Python多线程编程提供了有力的支持,踏实线程安全的。需要注意的是Queue模块中的对列和collections.dequeue所表示对额对列并不一样,前者主要用于不同线程之间的通信,它内部实现了现成的锁机制;而后者主要是数据结构上的概念,因此支持in方法
          再回过头来看看前面的例子,程序的实现有什么问题呢?答案很明显,作用于queue操作的条件变量完全是不需要的,因为Queue本身能够保证线程安全,因此不需要额外的同步机制。下面是一个多线程下载的例子
# -*- coding: utf-8 -*-
import os
import Queue
import threading
import urllib2
class DownloadThread(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        while True:
            url = self.queue.get()
            print self.name + "begin download"+url +"..."
            self.download_file(url)
            self.queue.task_done()
            print self.name + " download completed!!!"
    def download_file(self, url):
        urlhandler = urllib2.urlopen(url)
        fname = os.path.basename(url) + ".html"
        with open(fname, 'wb') as f:
            while True:
                chunk = urlhandler.read(1024)
                if not chunk:
                    break
                f.write(chunk)
if __name__ == "__main__":
    urls = [ "http://www.baidu.com", "http://www.cnblogs.com/chaosimple/p/4153083.html",
"http://shop.dspread.com/weixin/ksher/check_shop?page_no=1&page_count=10"]
    queue = Queue.Queue()
    for i in range(5):
        t = DownloadThread(queue)
        t.setDaemon(True)
        t.start()
    for url in urls:
        queue.put(url)
    queue.join()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • 1 队列简介 The Queue module has been renamed to queue in Pyth...
    rebirth_2017阅读 230评论 0 0
  • 1、线程和进程 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。 假定工厂的电力有限,一...
    文哥的学习日记阅读 14,334评论 0 9
  • 1、线程和进程 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。 假定工厂的电力有限,一...
    Andone1cc阅读 491评论 0 1
  • 淡淡的相遇,默默的守候 ——茹心 这个俗世有些吵杂,纷扰也很多,到处都是喧闹的声音。 脚掌不大,走路却很快,曾以为...
    茹心阅读 468评论 0 2