一、 前言
长期在做数据处理的工作,经常跑一些数据处理的脚本,因为需要跑的文件太大,为了充分的利用服务器的计算资源。往往需要编写多线程,多进程任务来缩短数据处理的时间(当然还有利用GPU运算也可以大大的缩短数据处理的时间)。一般来说,使用python编写多进程脚本有利于利用服务器的多核资源实现并行运算。
二、实验环境
python3..7
Anaconda 集成环境
操作系统:linux(cenos7.3)
依赖库
三、python的多进程与多线程概述
3.1 并行与并发概述
并发:并发的重点是它是一种现象,提供一种功能,让多个用户看起来多个程序运行起来。
并发是一种现象:同时运行多个程序或多个任务需要被处理的现象
并行: 多个程序同时运行,只有具备多个内核cpu才能够实现并行
并发可以看做一种“伪并行”,单个cpu加上多道技术也是可以实现并发(并行也属于并发);
如果只有单核cpu,多进程并行并没有提高效率
串行:是指一次只能取得一个任务并且执行这个任务;
3.2 python实现多进程编程(多进程编程适合于多核cpu)
python的多线程无法实现真正的并行运算,这主要是因为GIL锁的存在。因此python中的多线程是无法充分利用多核优势的。如果想要充分的利用多核cpu资源,就需要使用python的多进程编程。python有一个多进程包multiprocessing,可以很方便的实现程序的并行运算。
多进程:对于计算密集型任务,多进程占优势,计算密集型的任务需要利用多核CPU资源。
多线程: 对于 I/O 密集型任务,多线程占优势,由于I/O的阻塞性质,下载任务可能是多线程最佳的运用场景了。比如爬虫下载等等。
一般来说,一个程序基本上不会是纯计算或者纯 I/O,所以一般要写测试程序来确定任务用那种并发编程方式。
3.2.1 不使用进程池
我们下面主要就是利用multiprocessing 这个库来实现多进程编程:
一般用法如下:
multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
常用方法与参数设置:
start()启动进程
join() 实现进程间同步,等待所有进程退出
close()阻止多余的进程涌入进程池 Pool 造成进程阻塞。
target 是函数名字,需要调用的函数;
args 函数需要的参数,以 tuple 的形式传入
实验列程如下:
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 22 15:01:12 2019
@author: 13109
"""
from multiprocessing import Process
import time,os
def worker():
print("子进程{}执行中, 父进程{}".format(os.getpid(),os.getppid()))
time.sleep(2)
print("子进程{}终止".format(os.getpid()))
if __name__ == "__main__":
print("本机为",os.cpu_count(),"核 CPU")
print("主进程{}执行中, 开始时间={}".format(os.getpid(), time.strftime('%Y-%m-%d%H:%M:%S')))
start = time.time()
l=[]
# 创建子进程实例
for i in range(4):
p=Process(target=worker,name="worker{}".format(i),args=())
l.append(p)
# 开启进程
for i in range(4):
l[i].start()
# 阻塞进程
for i in range(4):
l[i].join()
stop = time.time()
print("主进程终止,结束时间={}".format(time.strftime('%Y-%m-%d%H:%M:%S')))
print("总耗时%s秒" % (stop - start))
上述代码在linux服务器下的运行结果如下:
(aminer) wss@iZuf60za0a748jc3c71epvZ:~/sites/Aminer/multi$ python untitled0.py
本机为 1 核 CPU
主进程20155执行中, 开始时间=2019-11-22 15:14:20
子进程20158执行中, 父进程20155
子进程20156执行中, 父进程20155
子进程20159执行中, 父进程20155
子进程20157执行中, 父进程20155
子进程20157终止
子进程20158终止
子进程20156终止
子进程20159终止
主进程终止,结束时间=2019-11-22 15:14:22
总耗时 2.0350992679595947 秒
(aminer) wss@iZuf60za0a748jc3c71epvZ:~/sites/Aminer/multi$
3.2.2 使用进程池来实现
进程池Pool类可以提供指定数量的进程供用户调用,当有新的请求提交至Pool中时,若进程池尚未满,就会创建一个新的进程来执行请求;若进程池中的进程数已经达到规定的最大数量,则该请求就会等待,直到进程池中有进程结束,才会创建新的进程来处理该请求。
实例代码如下:
# -*- coding: utf-8 -*-
"""
Created on Fri Nov 22 15:34:04 2019
@author: 13109
"""
from multiprocessing import Pool
import time,os
def worker(arg):
print("子进程{}执行中, 父进程{}".format(os.getpid(),os.getppid()))
time.sleep(2)
print("子进程{}终止".format(os.getpid()))
if __name__ == "__main__":
print("本机为",os.cpu_count(),"核 CPU")
print("主进程{}执行中, 开始时间={}".format(os.getpid(), time.strftime('%Y-%m-%d%H:%M:%S')))
start = time.time()
l = Pool(processes=5)#创建五条进程
# 向进程中添加10个任务
for i in range(10):
# l.apply(worker,args=(i,)) # 同步执行(Python官方建议废弃)
l.apply_async(worker,args=(i,)) # 异步执行
# 关闭进程池,停止接受其它进程
l.close()
# 阻塞进程,等待所有子进程结束
l.join()
stop = time.time()
print("主进程终止,结束时间={}".format(time.strftime('%Y-%m-%d%H:%M:%S')))
print("总耗时%s秒" % (stop - start))
实验结果如下:
(aminer) wss@iZuf60za0a748jc3c71epvZ:~/sites/Aminer/multi$ python untitled1.py
本机为 1 核 CPU
主进程20250执行中, 开始时间=2019-11-22 15:56:39
子进程20251执行中, 父进程20250
子进程20252执行中, 父进程20250
子进程20253执行中, 父进程20250
子进程20254执行中, 父进程20250
子进程20255执行中, 父进程20250
子进程20251终止
子进程20251执行中, 父进程20250
子进程20252终止
子进程20252执行中, 父进程20250
子进程20253终止
子进程20253执行中, 父进程20250
子进程20254终止
子进程20254执行中, 父进程20250
子进程20255终止
子进程20255执行中, 父进程20250
子进程20251终止
子进程20252终止
子进程20253终止
子进程20254终止
子进程20255终止
主进程终止,结束时间=2019-11-22 15:56:43
总耗时 4.044830799102783 秒
需要注意一下几点:
进程池Pool被创建出来后, p.apply_async(test) 语句不停地循环执行,相当于向进程池中提交了10个请求,它们会被放到一个队列中。
l = Pool(processes=5)#创建五条进程,执行完毕后创建了5条进程,但尚未给它们分配各自的任务;也就意味着,无论有多少任务,实际的进程数只有5条,每次最多5条进程并行。
当Pool中有进程任务执行完毕后,这条进程资源会被释放,Pool会按先进先出的原则取出一个新的请求给空闲的进程继续执行。
在创建Pool进程池时,若不指定进程的最大数量,默认创建的进程数为系统的内核数量
如果采用p.apply(test)阻塞方式添加任务,其每次只能向进程池中添加一条任务,然后for循环会被阻塞等待,直到添加的任务被执行完毕,进程池中的5个进程交替执行新来的任务,此时相当于单进程。
推荐使用applay-async方式来添加任务,异步方式,添加速度快。
3.2.3 多进程与不适用多进程耗时对比(一个数据处理运算任务)(待更新,暂时没有服务器资源)
使用多进程测试数据处理耗时对比:
import multiprocessing
import os
import pandas as pd
import csv
import time
from src.utils import Mono
start = time.time()
path = '/home/wss/sites/Aminer/task_10/'
mono = Mono(lang = 'en')
reader = pd.read_csv(path+'dso_doc.csv',error_bad_lines=False,iterator=True,encoding = 'utf-8')
def keyword_aminer(chunk,number_csv):
print('Run task (%s)...' % (os.getpid()))
start1 = time.time()
#print(chunk.columns)
#print(chunk.shape)
chunk['abstracts'].fillna('.',inplace = True)
chunk['summary'] = chunk['titleText']+' '+chunk['abstracts']
chunk.drop(columns=['titleText','abstracts'],inplace = True)
#print(chunk.columns)
wf = csv.writer(open(path + 'dso_test_{}.csv'.format(number_csv), 'w', encoding='utf-8', newline=''))
for jj in chunk.iterrows():
kk = mono.extract_keywords(str(jj[1]['summary']))
wf.writerow((jj[1]['id'],kk))
# chunks.append(chunk)
end = time.time()
print('task%srun%0.2fseconds.' % (os.getpid(), (end - start1)))
if __name__ == '__main__':
processor = 12#12
p = multiprocessing.Pool(processor)
for i in range(processor):
# a = p.apply_async(keyword_Aminer, args=(i,))
# res.append(a)
chunk = reader.get_chunk(1000000)
p.apply_async(keyword_aminer, args=(chunk,i,))
p.close()
p.join()
print("TIME USED:",time.time() - start)
喜欢 (1)
or
分享 (
0)