今天我们来聊聊Python里面的多进程与多线程编程模式。
01 多线程工作
在开始讲今天的正文之前,先给大家介绍一个概念「多线程工作」,这个概念可能有的人听过,也可能有的人平常工作中就是这么做的。我再来给大家讲讲这个概念,所谓的「多线程工作」就是同时做好几件事情。
拿我个人工作中例子来说,当我用Sql跑数的时候,数据不可能一下子就导出来,我会在一个屏幕上显示Sql运行进度,在另一个屏幕上先做一会PPT,等Sql跑出来以后,我就又会迅速切换到处理刚刚导出来的数据。有的时候数据量很大,用Excel打开文件可能需要几分钟的时间,这个时候Excel是处于运行状态,我是没法在Excel上做别的事情,我会去微信上去处理别人的一些问题,当文件打开以后,我会迅速切换到Excel上继续下一步处理,当我输入一个公式以后,Excel可能又需要等待一会,这个时候我就可以再去做一些别的事情。
大家可以看到,我没有在等一件事情彻底做完以后再去做另一件事情,而是在不同事情之间迅速切换,这种工作方式就可以算是一种「多线程工作」。
「多线程工作」可以减少你等待的时间,大大提高你的工作效率。
02 多进程与多线程
了解了「多线程工作」以后,我们开始进入今天的正题,编程里面的多线程和多进程。在上面的例子中Sql跑数可以算是一个进程、做PPT也可以算是一个进程、Excel处理数据还是一个进程。
进程下面还有一个更小的单位就是线程,一个进程由若干个线程组成,Sql跑数这个进程可以由写Sql、运行Sql、导出数据这几个线程组成。同样,PPT制作这个进程可以由明确主题、选模板、列大纲、丰富页面这几个线程组成。
线程是程序执行的最小单位,一个进程可以由一个或多个线程组成,各个线程之间也是交叉执行。
这里需要注意的是,多进程/多线程并不能做到同时去做好几件事情,而是把不同的事情交叉着做,做一段时间任务a,然后强制停止,去做一会任务b,再停止,再去做任务c。之所以会觉得各个任务之间是同时进行的原因是是任务与任务之间切换速度足够快,这样看起来就像是多个任务同时在进行。
我们再来看两个概念:并行:指在同一时刻,有多条指令在多个处理器上同时执行;
并发:指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
多份工作有多个人同时在做时就是并行,当多份工作由一个人交替在做时就是并发。在计算机中也是同样的概念,计算机中CPU的核数就相当于人数,当计算机是单核多任务时就是并发;当计算机是多核且大于任务数时,就是并行。
目前电脑主流配置都是四核/八线程的,而实际工作的任务数大都大于四个,所以也是需要交替来执行具体任务的,也就是并发执行。
上面就是关于多线程与多进程的一个简单通俗的理解,一些太官方的解释我就不在这里放了,大家感兴趣的可以去自行上网查。
03 多进程与多线程是如何提高效率的
假设做任务A需要1个小时、任务B需要1个小时、任务C需要一个小时,当我们每个任务做20分钟以后切换到另一个任务,这样做完三个任务需要的总时间是不会变的,不仅不会变,反而可能会增加,因为在不同任务之间切换是需要代价的,因为当你从一个任务切换到另一个任务时很有可能不记得刚刚做到哪里了,还需要花时间想一想。那既然是这样,我们为什么还要用多进程/多线程这种处理任务的方式呢?我在第一小节里面提过,「多线程工作」可以减少你等待的时间,大大提高你的工作效率。
是因为在实际工作中,有很多需要等待的地方,比如等待Excel打开,等待Sql跑出数据。多进程/多线程任务处理方式就是充分利用这些等待时间。让你的大脑,计算机的大脑(CPU)得到充分的利用。如果要是没有等待的时间,多进程/多线程的任务处理方式可能就不如单线程的了。
04 多进程与多线程是如何实现的
了解清楚了多进程与多线程是什么,以及是如何提高处理任务的效率的以后,我们进入到硬干货部分,那就是具体多进程/多线程如何实现“同时”处理多任务的。
实现多任务的方式主要有以下几种:
1、多进程模式
2、多线程模式
3、多进程+多线程
同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂停等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。
4.1多进程模式
多进程就是一次启动多个进程,每个进程只有一个线程,但多个进程可以一起执行多个任务。一般进程数默认是电脑CPU核数,当你的电脑是四核的时候,你的电脑进程默认就是4个。
4.1.1参数详解
在Python中我们借助多进程包multiprocessing来进行多进程任务处理方式, multiprocessing模块提供了一个Process类来代表一个进程对象,
#Process参数
multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
#group分组
#target表示调用对象,即函数
#name表示进程的别名
#args表示调用对象的位置参数元组,即函数的参数
#kwargs表示调用对象的字典
#Process常用方法
close() 关闭进程
is_alive() 进程是否在运行
join() 等待join语句之前的所有程序执行完毕以后再继续往下运行,通常用于进程间的同步
start() 进程准备就绪,等待CPU调度
run() strat()调用run方法,如果实例化进程时没有传入target参数,这star执行默认run()方法
#Process常用属性
pid 进程ID
name 进程名字
4.1.2建立一个子进程
下面的例子演示了启动一个子进程(即单进程)并等待其结束:
from multiprocessing import Process
import os
# 子进程要执行的代码def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())#用来获取主进程的进程ID
p = Process(target=run_proc, args=('test',))#实例化进程p,调用run_proc函数,传入参数对象args
print('Child process will start.')
p.start()#进程准备就绪
p.join()#待所有进程执行完毕以后执行后续操作
print('Child process end.')
运行结果如下:
Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.
一个子进程其实就和我们平常调用单一函数是一样的。
4.1.3建立多个子进程
建立多个子进程(即多进程),其实就是多个函数随机同步运行。
建立多进程有两种方法,一种是直接利用Process来建立多个子进程即可,如下:
from multiprocessing import Process
import random,time
def do_task(task):
print('我正在做{}'.format(task))
time.sleep(random.randint(1,3))
def write_task(task):
print('我正在写{}'.format(task))
time.sleep(random.randint(1,3))
if __name__ == "__main__":
p1 = Process(target=do_task,args=('PPT',))
p2 = Process(target=write_task,args=('Sql',))
p1.start()
p2.start()
输出结果为:
我正在做PPT
我正在写Sql
上面代码表示同时启动两个进程,且两个进程分别调用不同的函数,即做不同的任务。而且上面的任务数只有两个,当任务数(需要调用的函数)较多时,我们如果还用上述的方法创建多进程,就需要实例化多个进程对象,并且写多行p.start()比较麻烦,聪明的前辈们肯定不会用这么笨的方法,所以就有了进程池(Pool)。
multiprocessing.Pool = Pool(processes=None)#process为进程数
把上面的二进程用进程池表示以后的结果如下:
import multiprocessing
import random,time
def do_task(task):
print('我正在做{}'.format(task))
time.sleep(random.randint(1,3))
def write_task(task):
print('我正在写{}'.format(task))
time.sleep(random.randint(1,3))
if __name__ == "__main__":
func_list=[do_task,write_task]
args_list=["PPT","Sql"]
pool=multiprocessing.Pool(2)
for func,args in function_list,args_list:
pool.apply_async(func,arg)
print 'Waiting for all subprocesses done...'
pool.close()
pool.join() #调用join之前,一定要先调用close() 函数,否则会出错
print 'All subprocesses done.'
输出结果如下:
Waiting for all subprocesses done...
我正在做PPT
我正在写Sql
All subprocesses done.
4.2多线程模式
多线程模式就是一次只启动一个进程,但是在这个进程里面可以启动多个线程,这样多个线程就可以一起执行多个任务,在Python中我们要启动多线程借助于threading模块,用于 启动多线程的模块还有_thread模块,但是threading模块是封装了_thread模块,且比较高级,所以我们一般使用threading模块即可。
4.2.1参数详解
启动多线程使用的是threading模块中的Thread类,构建时使用的参数和方法与Process基本一致,大家看看即可,这里就不赘述了。
#参数
Thread(group=None, target=None, name=None, args=(), kwargs={})
#方法
isAlive()
get/setName(name) 获取/设置线程名
start()
join()
4.2.2创建一个线程
创建一个线程就是调用一个函数。
import time, threading
def do_chioce(task):
print('我正在{}'.format(task))
time.sleep(random.randint(1,3))
if __name__ == "__main__":
t = threading.Thread(target=do_chioce,args=('选PPT模板',))
t.start()
输出结果为:
我正在选PPT模板
4.2.3创建多个线程
创建多个线程就是调用多个函数。
import time, threading
def do_chioce(task):
print('我正在{}'.format(task))
time.sleep(random.randint(1,3))
def do_content(task):
print('我正在{}'.format(task))
time.sleep(random.randint(1,3))
if __name__ == "__main__":
t1 = threading.Thread(target=do_chioce,args=('选PPT模板',))
t2 = threading.Thread(target=do_content,args=('列PPT大纲',))
t1.start()
t2.start()
输出结果为:
我正在选PPT模板
我正在列PPT大纲
4.3多进程+多线程
多进程+多线程就是一次启动多个进程,每个进程又启动多个线程,这样同时执行的任务就会很多,但是模型相对复杂,不建议使用。