如何使用多处理实现Producer Consumer?

时间:2018-04-23 04:00:46

标签: python multiprocessing python-multiprocessing

我有一个程序,我需要从某些来源下载文件并上传。但我需要确保下载位置最多有10个文件。有没有办法使用Managers()?

这听起来像一个典型的制片人 - 消费者问题。以下是我的计划。

以下是我的实施

from multiprocessing import Process, Queue, Lock
import requests
import json
import shutil
import os
import time
import random
import warnings
warnings.filterwarnings("ignore")

sha_list = [line.strip() for line in open("ShaList")]


def save_file_from_sofa(sha1):
    r = requests.get("https://DOWNLOAD_URL/{}".format(sha1), verify=False, stream=True)
    with open(sha1, 'wb') as handle:
        shutil.copyfileobj(r.raw, handle)


def mock_upload():
    time.sleep(random.randint(10,16))


def producer(queue, lock):
    with lock:
        print("Starting Producer {}".format(os.getpid()))

    while sha_list:
        if not queue.full():
            sha1 = sha_list.pop()
            save_file_from_sofa(sha1)
            queue.put(sha1)


def consumer(queue, lock):
    with lock:
        print("Starting Consumer {}".format(os.getpid()))

    while True:
        sha1 = queue.get()
        mock_upload()
        with lock:
            print("{} GOT {}".format(os.getpid(), sha1))

if __name__ == "__main__":
    queue = Queue(5)
    lock = Lock()

    producers = [Process(target=producer, args=(queue, lock)) for _ in range(2)]
    consumers = []

    for _ in range(3):
        p = Process(target=consumer, args=(queue, lock))
        p.daemon = True #Do not forget to set it to true
        consumers.append(p)

    for p in producers:
        p.start()
    for c in consumers:
        c.start()

    for p in producers:
        p.join()

    print("DONE")

但它没有达到预期效果,正如您可以从下面的输出中看到的那样

启动制作人623

启动制作人624

启动消费者626

启动消费者625

启动消费者627

626 GOT 4ff551490d6b2eec7c6c0470f4b092fdc34cd521

625 GOT 83a53a3400fc83f2b02135ba0cc6c8625ecc7dc4

627 GOT 4ff551490d6b2eec7c6c0470f4b092fdc34cd521

626 GOT 83a53a3400fc83f2b02135ba0cc6c8625ecc7dc4

625 GOT 4e7132301ce9d61445db07910ff90a64474e6a88

626 GOT 0efbd413d733b3903e6dee777ace5ef47a2ec144

627 GOT 4e7132301ce9d61445db07910ff90a64474e6a88

625 GOT 0efbd413d733b3903e6dee777ace5ef47a2ec144

626 GOT 0a3fc4bdd56fa2bf52f5f43277f3b4ee0f040937

625 GOT eb9c07329a8b5cb66e47f0dd8e56894707a84d94

627 GOT 0a3fc4bdd56fa2bf52f5f43277f3b4ee0f040937

626 GOT eb9c07329a8b5cb66e47f0dd8e56894707a84d94

DONE

正如您所见,消费者多次获取相同的SHA1。所以,我需要一个程序来确保生产者放入队列的所有SHA1只被一个消费者选中。

P.S我也曾想过用池做它。对于生产者来说它可以正常工作,因为我已经有了要放入队列的SHA1列表,但是对于消费者,我如何使用任何列表来确保消费者实际上正在停止。

1 个答案:

答案 0 :(得分:0)

只需使用multiprocessing.Poolconcurrent.futures中的游泳池即可。该池允许您设置要同时运行的工作人员数量。这意味着您将同时下载最多max_workers个文件。

由于下载/上传是顺序的(在下载完成之前无法开始上传),因此在两个独立的线程/进程中运行它们没有任何价值。只需将两个操作连接在一个作业单元中,然后同时运行多个作业。

此外,只要您只需要下载/上传文件(IO绑定操作),您最好使用线程而不是进程,因为它们更轻量级。

from concurrent.futures import ThreadPoolExecutor

list_of_sha1s = ['foobar', 'foobaz']

def worker(sha1):
    path = save_file_from_sofa(sha1)
    upload_file(path)

    return sha1

with ThreadPoolExecutor(max_workers=10) as pool:
    for sha1 in pool.map(worker, list_of_sha1s):
        print("Done SHA1: %s" % sha1)