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

  之前在写繁体字转简体字的时候,由于数据量比较大,所以用了多进程来实现。其实我对多进程/多线程的认识只是了解概念,第一次看到实际的应用是在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实现多进程+进度条显示)