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

2016-10-12  本文已影响0人  DraculaWong

      曾经有这么一个说法,程序中存在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种队列:

# -*- 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()
上一篇下一篇

猜你喜欢

热点阅读