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×单任务耗时

思考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270