美文网首页
Python实现多进程+进度条显示

Python实现多进程+进度条显示

作者: 湯木 | 来源:发表于2020-05-01 12:09 被阅读0次

      之前在写繁体字转简体字的时候,由于数据量比较大,所以用了多进程来实现。其实我对多进程/多线程的认识只是了解概念,第一次看到实际的应用是在BDCI-OCR的项目中,作者用多进程进行图像处理。毫无疑问,并行计算能显著地减少运行时间。
      那么为什么用多进程实现并行计算(多核任务),不用多线程呢?

    在Python中用多进程实现多核任务的原因

      因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
      GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
      所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
      不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

    引用链接

    多进程示例:

      网上有很多实现多进程的示例,我只记录自己用过的。

    from multiprocessing import Pool, cpu_count
    import os, time, random
    
    
    def long_time_task(name):
        print('执行任务%s (%s)...' % (name, os.getpid()))
        start = time.time()
        time.sleep(random.random() * 3)
        end = time.time()
        print('任务 %s 运行了 %0.2f seconds.' % (name, (end - start)))
    
    
    if __name__ == '__main__':
        print('父进程 %s.' % os.getpid())
        pool_num = cpu_count()  # 获取当前CPU最大核数
        pool = Pool(pool_num)
        for i in range(10):
            pool.apply_async(func=long_time_task, args=(i,))  # 异步非阻塞
        print('等待所有子进程结束...')
        pool.close()
        pool.join()
        print('所有子进程均结束')
    

      这里我用的是pool.apply_async(),是异步非阻塞的方法,可以理解为:不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。当然,还有其他方法,网上有很多资料,我就不赘述了。

    运行结果:

    父进程 17416.
    等待所有子进程结束...
    执行任务0 (25952)...
    执行任务1 (24756)...
    执行任务2 (30032)...
    执行任务3 (22148)...
    执行任务4 (7252)...
    执行任务5 (10828)...
    执行任务6 (14448)...
    执行任务7 (24564)...
    任务 2 运行了 0.17 seconds.
    执行任务8 (30032)...
    任务 5 运行了 0.27 seconds.
    执行任务9 (10828)...
    任务 9 运行了 0.24 seconds.
    任务 7 运行了 1.08 seconds.
    任务 1 运行了 1.61 seconds.
    任务 4 运行了 1.70 seconds.
    任务 8 运行了 2.01 seconds.
    任务 3 运行了 2.50 seconds.
    任务 0 运行了 3.01 seconds.
    任务 6 运行了 2.91 seconds.
    所有子进程均结束
    

      从运行结果中可以发现:因为cpu最大核心数是8,所以前8个任务的进程id都不一样,任务9的进程id与任务2的相同,即任务2执行结束后再执行任务9,依此类推。

    进度条示例:

      模拟的事件:共需处理10个任务,每个任务执行时间为5秒(5 * time.sleep(1))

    from multiprocessing import Pool, cpu_count
    import os, time, random
    from tqdm import tqdm
    
    
    class MyMultiprocess(object):
        def __init__(self, process_num):
            self.pool = Pool(processes=process_num)
    
        def work(self, func, args):
            for arg in args:
                self.pool.apply_async(func, (arg,))
            self.pool.close()
            self.pool.join()
    
    
    def func(num):
        name = num
        for i in tqdm(range(5), ncols=80, desc='执行任务' + str(name) + ' pid:' + str(os.getpid())):
            # time.sleep(random.random() * 3)
            time.sleep(1)
    
    
    if __name__ == "__main__":
        print('父进程 %s.' % os.getpid())
        mymultiprocess = MyMultiprocess(cpu_count())
        start = time.time()
        mymultiprocess.work(func=func, args=range(10))
        end = time.time()
        print("\n应用多进程耗时: %0.2f seconds" % (end - start))
    
        start = time.time()
        for i in range(10):
            func(i)
        end = time.time()
        print("\n不用多进程耗时: %0.2f seconds" % (end - start))
    

    参考链接

    运行结果:

    父进程 20412.
    执行任务0 pid:16144: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务1 pid:24464: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务2 pid:17732: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务4 pid:11136: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务5 pid:27844: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务3 pid:17288: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务6 pid:26504: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务7 pid:28256: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务8 pid:16144: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务9 pid:24464: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    应用多进程耗时: 11.59 seconds
    执行任务0 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务1 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务2 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务3 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务4 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务5 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务6 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务7 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务8 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    执行任务9 pid:20412: 100%|██████████| 5/5 [00:05<00:00,  1.01s/it]
    不用多进程耗时: 50.58 seconds
    

      发现:因为我的cpu是8核,所以10个任务的多进程耗时约为2×单任务耗时

    思考

      在查阅相关资料时发现,多进程在实际使用的时候有单参数多参数之分,那么多参数和单参数的优缺点分别是什么呢?

    相关文章

      网友评论

          本文标题:Python实现多进程+进度条显示

          本文链接:https://www.haomeiwen.com/subject/eetowhtx.html