Python3 multiprocessing joinable queue 模板

最近需要在服务器上处理一批文件,每个文件的处理过程很简单,基本就是读入文件,计算一些统计值,然后把统计值汇总。一想这可以多线程啊老铁!调试了一下Python3的multiprocessing,这里留下一个模板以备之后使用。

程序运行的逻辑是这样的

  • 主进程扫描需要处理的文件,生成文件列表。
  • 主进程创建job队列和result队列。此时队列都为空。
  • 主进程创建所有子进程。子进程启动。监听来自job队列的信息(blocked get())。
  • 主进程将文件列表的内容逐一put到job队列内(JoinableQueue)。子进程get来自job队列的信息,处理文件,将处理结果put到result队列。
  • 主进程开始从result队列get数据并临时存储。
  • 主进程将所有job发送完毕。主进程将所有result队列的数据get完毕。主进程join job队列。
  • 主进程发送终止标志给所有子进程。
  • 子进程终止。
  • 主进程join所有子进程。
  • 主进程开始处理所有result(根据文件次序排序,等等)。
  • 主进程退出。

调试过程还是比较顺利的,之前也简单使用过python自带的多线程工具。期间遇到一个问题,就是主进程先发送了终止标志给子进程,然后才开始从result队列获取数据,导致主进程在从result队里get数据时形成无限block。其原理基本是这样的:

  • 子进程通过result队列put信息,当result队列已经有过多尚未get的数据时,子进程put的信息被一个pipe缓冲起来,等待result队列有更多空间时再转入队列。
  • 若主进程没有及时从result队列get数据,导致result队列有尚未进入的缓冲数据,并且主进程发送终止标志给子进程,子进程在未完成result队列的put的情况下退出,导致数据丢失,数据没有及时进入result队列。

Lesson learned: 利用get() 处理队列的进程要保持开启直到所有需要put()的数据都已put并且get到队列为空,此时才能安全地终止调用put()的进程。

此外,Python3对Queue package的命名与Python2不同,处理异常时需要注意。

以下是模板源码。注意必须为Python3执行。


# Author: Yaoyu Hu <yyhu_live@outlook.com>

import argparse
import multiprocessing
from queue import Empty
import time

def cprint(msg, flagSilent=False):
    if ( not flagSilent ):
        print(msg)

def process_single_file(name, jobStr, flagSilent=False):
    """
    name is the name of the process.
    """

    startTime = time.time()

    cprint("%s. " % (jobStr))

    endTime = time.time()
    
    s = "%s: %ds for processing." % (name, endTime - startTime )

    cprint(s, flagSilent)
    cprint("%s: " % (name), flagSilent)

    return s

def worker(name, q, p, rq, flagSilent=False):
    """
    name: String, the name of this worker process.
    q: A JoinableQueue.
    p: A pipe connection object. Only for receiving.
    """

    cprint("%s: Worker starts." % (name), flagSilent)

    while (True):
        if (p.poll()):
            command = p.recv()

            cprint("%s: %s command received." % (name, command), flagSilent)

            if ("exit" == command):
                break

        try:
            jobStrList = q.get(True, 1)
            # print("{}: {}.".format(name, jobStrList))

            s = process_single_file(name, jobStrList[0], flagSilent)

            rq.put([s], block=True)

            q.task_done()
        except Empty as exp:
            pass
    
    cprint("%s: Work done." % (name), flagSilent)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Filter the files.")

    parser.add_argument("--jobs", type=int, default=100, \
        help="The number of jobs for testing.")

    parser.add_argument("--np", type=int, default=2, \
        help="The number of processes.")

    args = parser.parse_args()

    assert args.jobs > 0
    assert args.np > 0

    startTime = time.time()

    print("Main: Main process.")

    jobQ    = multiprocessing.JoinableQueue()
    resultQ = multiprocessing.Queue()

    processes = []
    pipes     = []

    print("Main: Create %d processes." % (args.np))

    for i in range(int(args.np)):
        [conn1, conn2] = multiprocessing.Pipe(False)
        processes.append( multiprocessing.Process( \
            target=worker, args=["P%03d" % (i), jobQ, conn1, resultQ, False]) )
        pipes.append(conn2)

    for p in processes:
        p.start()

    print("Main: All processes started.")

    for dj in range(args.jobs):
        jobQ.put([ str(dj) ])

    print("Main: All jobs submitted.")

    resultList = []
    resultCount = 0

    while(resultCount < args.jobs):
        try:
            print("Main: Get index %d. " % (resultCount))
            r = resultQ.get(block=True, timeout=1)
            resultList.append(r)
            resultCount += 1
        except Empty as exp:
            if ( resultCount == args.jobs ):
                print("Main: Last element of the result queue is reached.")
                break

    jobQ.join()

    print("Main: Queue joined.")

    for p in pipes:
        p.send("exit")

    print("Main: Exit command sent to all processes.")

    for p in processes:
        p.join()

    print("Main: All processes joined.")

    print("Main: Starts process the result.")

	print(resultList)

    endTime = time.time()

    print("Main: Job done. Total time is %ds." % (endTime - startTime))
发布了77 篇原创文章 · 获赞 36 · 访问量 14万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览