Anne的python私房菜(9)---- 并行与多任务

本章主要解决如何让自己的程序跑的快的问题

1. 什么是并行

打开任务管理器,选择性能栏,我们能看到代表CPU占用率的项目由若干个小框框构成;这便是当今市面上主流的多核处理器。我们的操作系统会将当前所需要完成的任务分配到各个处理器核心中,使得电脑能够同时执行多种任务,例如听歌、码字、看视频等。用计算机术语来讲,我们交给电脑的每个任务都是一个进程(Process),例如当我们打开浏览器,我们就启动了一个浏览器进程;打开pycharm,我们就启动了一个pycharm进程。在每个进程内,也会有多种需要完成的工作,我们把这种进程内的小任务称为线程(Thread),例如打开网易云听日推,网易云这个进程内会有许多线程完成多种任务:有些线程与服务器沟通获取你的日推歌单,有些线程播放当前的音乐,当然也会有给你推送广告的线程(摔。

总而言之,我们的电脑由多个进程组成来同时完成多种任务,而每个进程又由许多线程组成。设计程序构造若干进程或线程来通力完成某个目标,便是并行的核心。

2. 并行的实现策略

以Anne的绘图程序为例,我们分析一下程序总体运行规律

设计的循环中,每个循环都会先从JSON中取一个城市名,使用这个城市名读取数据,画图。保存图片之后读取下一个城市的信息,继续循环。但是这种运行方法每循环中只能占用一个CPU核,其他几个核基本上浪费了。同时我们可以发现,每个城市的画图与分析工作是相互独立互不干扰的,那我们可不可以通过产生若干进程让这几个城市的计算操作同时进行呢?例如下图


这样,我们就可以同时实现三个城市的画图工作啦,之前画一个城市的时间我们就可以画三个城市了,开不开心,惊不惊喜。但是,问题也随之而来,我们之前设计了一个循环,每次循环读取一个城市名,再通过这个城市名完成画图的操作。但假如我们现在设计三个城市同时画图,循环操作还能否实现?最直观的方法是我们每个循环取三个城市出来,设计一个判断条件,待到三个城市都画完再进入到下个循环,但这样的话,会有什么问题呢?

如图,假如一个城市耗时特别久,那么它便会极大拖累整个循环的进程,使得效率降低。怎么解决呢?我们需要找一个管事的。 这个管事的能够根据目前各进程完成任务的情况自动给进程分配任务,基本效果如图

这个管事的能储存所有的任务,各个进程在完成自己的任务后便会到管事的那里去领新的任务,从而实现任务的具体分配,又因为这种分配模式就像排队一样,谁排到算谁的,这种管事模式又叫做队列(Queue)。下面,我们就要学习整个模型的具体实现了。

3. Python实现

通过以上讲述,我们已经了解到实现并行需要如下步骤

  1. 生成一个管事的任务管理模块
  2. 根据需求创建若干进程
  3. 进程与管事的之间沟通获取任务

看起来很麻烦,但是python已经为我们提供了一个好用的库multiprocessing库文档,通过这个库,我们就可以快速构建一个多任务体系,废话少说,先上总体框架。


import multiprocessing as mp

def fun(jobs):
	while not jobs.empty():
		currentjob = jbs.get()
		#  currentjob便是当前分配来的任务,根据这个任务实现你的代码
		
if __name__ == '__main__':
    jobs = mp.Queue()
    # 创建一个队列
    df = pd.read_csv("city_capi_pop800.csv", encoding='gbk')
    citys = df['city']
    # 从json里获取所有的城市
    for city in citys:
        jobs.put(city)
        # 所有城市放入队列中
    for i in range(16):
        proc = mp.Process(target=fun, args=(jobs,))
        # 创建进程,这个进程执行fun函数,参数为jobs
        proc.start()
        # 进程开始工作

1. 创建队列


if __name__ == '__main__':
    jobs = mp.Queue()
    # 创建一个队列
    df = pd.read_csv("city_capi_pop800.csv", encoding='gbk')
    citys = df['city']
    # 从json里获取所有的城市
    for city in citys:
        jobs.put(city)
        # 所有城市放入队列中

这里我们借用之前的循环,先获取所有的城市名,将其字符串作为任务储藏在创建的队列中。

2. 创建、开始进程

for i in range(16):
	proc = mp.Process(target=fun, args=(jobs,))
	# 创建进程,这个进程执行fun函数,参数为jobs
	proc.start()
	# 进程开始工作

这里我们通过一个循环的方式创建若干个进程,循环数可根据自己电脑性能自行修改,通常为核数x2-1. 注意:mp.Process()这个创建函数首个参数target=不带括号的目标函数名!,第二个参数args=()是目标函数需要的参数,由于我们的目标函数只需要任务队列,所以直接把jobs传进去。
创建完进程还需要start()一下,不然进程不会工作。

3.目标函数设计

def fun(jobs):
	while not jobs.empty():
		currentjob = jobs.get()
		#  currentjob便是当前分配来的任务,根据这个任务实现你的代码

传入队列jobs后,我们主要通过循环的方式来持续获取任务,直到队列里没有任务之后循环终止,该进程也随之销毁。任务获取通过.get()方法获取,每调用一次该函数,我们就从队列中取出一个“任务”,在本例中为字符串。之后的操作就与之前的常规画图操作无异了。

当然,还有基于多线程的并行方法,但是其相对较为复杂且对程序条件要求高,我们暂不考虑。

最简单的并行操作就是这样啦,希望能帮到宝贝!

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注