python多进程线程学习_Python多进程与多线程编程(学习笔记)

一、 前言

长期在做数据处理的工作,经常跑一些数据处理的脚本,因为需要跑的文件太大,为了充分的利用服务器的计算资源。往往需要编写多线程,多进程任务来缩短数据处理的时间(当然还有利用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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值