python Pool+Queue 实现消费者-生产者模型

python Pool+Queue 实现消费者-生产者模型

之前做了一个邮件分类的小task,需要解析的邮件有80w封左右,单一进程解析速度太慢,又因为python的GIL特性,所以选择了多进程的方式,提高cpu多核的利用率来加速邮件解析的速度。

生产者与消费者模型

生产者与消费者模型常用于处理并发问题,生产者用于生产数据,消费者用于获得数据,处理数据。

#encoding: utf-8
from multiprocessing import Process, Manager
import time

def create_data(q):
    # data_source用于模拟我们有1w条数据。
    data_source = (i for i in range(10000))
    # 读取数据
    for data in data_source:
        time.sleep(0.001)
        print 'get data %s' % (data,)
        # 将读取的数据放入队列之中,,以方便别人来处理。
        q.put(data)
    # 1w条数据读取完后,将None作为结束信号放入队列中,告诉别人没数据了,可以结束了。
    q.put(None)

def handle_data(q):
    # 读取队列里的数据,并且处理他们。
    while True:
        # 队列的get方法,可以从队列里获取数据,他是阻塞的方法,如果队列中没有数据,他会一直等待。
        # 当然你也可以设置他的超时时间,一旦超时,会抛出Empty异常。非阻塞式的调用可以使用
        # q.get(False) 或者 q.get_nowait(), 当然队列没数据的话,他们也会抛Empty异常。
        data = q.get()
        # 是否处理完成
        if data == None:
            print 'end'
            break
        time.sleep(0.002)
        # 处理数据,这里只是模拟了一下,象征性的每个数据乘了2,在具体的业务中,你可能会有其他处理数据的操作,
        # 比如将数据存入数据库等。
        result = data * 2
        print 'handle data %s, result: %s' % (data, result)


if __name__ == '__main__':
    data_queue = Manager().Queue()
    # 创建一个生产者的进程,用于生产数据。
    producer = Process(target=create_data, args=(data_queue,))
    # 创建一个消费者的进程,用于处理数据。
    consumer = Process(target=handle_data, args=(data_queue, ))
    # 启动进程
    producer.start()
    consumer.start()
    # 使当前进程,也就是main进程,进入等待状态,当p进程都执行完毕后,
    # 当前进程才会继续下去运行。
    producer.join()
    consumer.join()

通过上面的小例子,可以看出采用多进程的方式能够提高程序的运行速度,不过因为木桶效应,整个程序执行完成的时间取决于运行速度最慢的那一个进程。生产者生产一个数据耗时0.001秒,而消费者处理一个数据需要0.002秒,只有消费者执行完毕后,整个程序才能执行完成。可以看出,我们如果能加速消费者进程的执行,便能让整个程序更快的执行完成。优化的方式有很多种,比如优化处理数据的算法,缩短处理每个数据的时间,如果处理单个数据的时间无法缩短,可以再多开一个进程去帮助消费者进程处理数据。

下面的小例子是通过使用进程池+队列+生产者与消费者模型来并发的处理邮件,因为处理数据的速度慢,所以多开了几个进程去处理数据,进程也不宜开多,数量取决于自己电脑的cpu核心数。

#encoding: utf-8
from multiprocessing import Pool, Manager
from time import sleep, time
import csv

# 模拟一共有1W封邮件
EMAIL_SOURCE = (i for i in range(0,10000))

EMAIL_WORKER_DATA = {}
# worker进程数,别设置多了,和自己的cpu核心数有关。
MAX_WORKER_NUM = 3
RESULT_DATA = Manager().Queue()

# 生产者与消费者模型的应用

def _init_worker_data():
    # 为每一个worker进程分配自己的队列
    for i in range(0, MAX_WORKER_NUM):
        EMAIL_WORKER_DATA[i] = Manager().Queue()

# 读取email的数据,并放入每个worker对应的队列里。
def _read_email_data():
    index = 0
    for data in EMAIL_SOURCE:
        sleep(0.0003)
        print 'load data: %s' % (data)
        q = EMAIL_WORKER_DATA[index % MAX_WORKER_NUM]
        q.put(data)
        index += 1
    for q in EMAIL_WORKER_DATA.itervalues():
        q.put(None)

# 处理队列里的数据,将结果录入结果队列里
def _read_worker_data(worker_id):
    while True:
        sleep(0.003)
        q = EMAIL_WORKER_DATA[worker_id]
        data = q.get()
        if data == None:
            print 'finished'
            RESULT_DATA.put(data)
            break
        print '%s gets data: %s' % (worker_id, data)
        RESULT_DATA.put(data)

# 从结果队列里读取数据,并写入csv文件里,方便以后分析数据。
def _read_result_data():
    file_name = 'test.csv'
    f = open(file_name, 'w')
    writer = csv.writer(f)
    writer.writerow('data')
    while True:
        data = RESULT_DATA.get()
        if data == None:
            print 'finished'
            f.close()
            break
        writer.writerow([data])
        print 'handled result data:%s' % (data,)

if __name__ == '__main__':
    start_time = time()
    _init_worker_data()
    p = Pool(MAX_WORKER_NUM + 2)
    # apply_async,开启一个子进程去执行任务,他是非阻塞式的,
    # 执行到了这条语句后,主程序会继续向下运行,不会被阻塞。
    p.apply_async(_read_email_data)
    for i in range(0, MAX_WORKER_NUM):
        p.apply_async(_read_worker_data, args=(i,))
    p.apply_async(_read_result_data)
    # 关闭进程池,不再接受新的进程
    p.close()
    # 主进程阻塞等待子进程的退出,close要先于join使用。
    p.join()
    end_time = time()
    print 'handle time:%ss' % (end_time - start_time)
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值