python中多进程及multiprocessing进程池使用——批量读取excel文件

首先准备:当前文件上级目录下有个excels目录,目录里存在15份.xls文件,每个文件1000条数据,需要通过多进程对这些文件读取为pandas的dataframe格式

手动创建多进程读取文件(进程数等于任务数)

# @datetime:6/26/0026
"""通过多进程加速读取excel的测试"""
__author__ = "[email protected]"
import os.path
import time
from service import logger
import pandas as pd
from multiprocessing import Process, Manager
startTime = time.time()

logger = logger.MyLogger("multi_process").getLogger()


def getExcelData(path, return_data=None, file_name=""):
    global startTime
    logger.info("开始读取Excel文件,当前进程pid:" + str(os.getpid()))
    if not os.path.exists(path):
        raise FileNotFoundError()
    if os.path.isfile(path):
        return_data[file_name] = pd.read_excel(path, skiprows=1, skipfooter=1)
        logger.info("读取Excel文件完毕,当前进程pid:" + str(os.getpid()))

if __name__ == "__main__":
    excel_path = os.path.join(os.getcwd(), "../excels")
    xls_names = [x for x in os.listdir(excel_path) if x.endswith(".xls")]
    first = str(time.time() - startTime)
    logger.info("进入程序用时:" + first)
    p_list = []
    # Manager类似于同步数据管理工具,可在多进程时实现各进程操作同一个数据,比如这里通过它组织返回值
    manager = Manager()
    # Manager.dict()类似于共享变量,各个进程可以修改它,通过每次添加不同的key值,可以实现方法返回值的获取
    return_data = manager.dict()
    first = time.time() - startTime
    # 手动创建多个进程读取,可能存在创建进程过多导致系统崩溃的情况
    for file_name in xls_names:
        p = Process(target=getExcelData, args=(os.path.join(excel_path, file_name), return_data, file_name))
        p.start()
        p_list.append(p)
    print(p_list)

    """
    经测试,直到这里都还会延迟数秒才执行进程的target方法,尽管前面已经调用了start(),但进程并没有立即执行
    寡人认为是系统创建进程需要时间,并且是创建好所有进程后才各进程才开始工作,这里要创建120个进程花费了大多数的时间
    后面在采用进程池时,当设置最大进程数为120时,依然花费了大把的时间,而设置为10时,大大缩小了创建进程到执行target方法所要等待的时间
    这也证明了寡人的观点,至于正确与否,寡人先跟代码去了,且等下回分解
    """
    for p in p_list:
        # 如果有子进程没有执行完,需要先阻塞主进程
        p.join()
    logger.info("各进程执行完毕")
    # 获取返回值字典为列表
    data_frames = return_data.values()
    # 合并列表为一个dataFrame
    data = pd.DataFrame()

    for da in data_frames:
        data = data.append(da)

    endTime = time.time()
    print(endTime - startTime)
    print(len(data))

输出:

耗时:21.496851205825806

本次测试耗时较多,主要问题出现在调用process.start()方法后,对应的进程并没有立即执行target方法,而是进行了较长时间的等待线程创建完毕

使用multiprocessing的Pool进程池读取

# @datetime:6/26/0026
"""通过多进程加速读取excel的测试"""
__author__ = "[email protected]"
import os.path
import time
from service import logger
import pandas as pd
from multiprocessing import Pool

logger = logger.MyLogger("multi_process").getLogger()


def getExcelData(path):
    logger.info("开始读取excel,当前进程pid:" + str(os.getpid()))
    data = pd.DataFrame()
    if not os.path.exists(path):
        raise FileNotFoundError()
    if os.path.isfile(path):
        logger.info("读取Excel文件完毕,当前进程pid:" + str(os.getpid()))
    return data.append(pd.read_excel(path, skiprows=1, skipfooter=1), sort=False)


if __name__ == "__main__":
    excel_path = os.path.join(os.getcwd(), "../excels")
    xls_names = [x for x in os.listdir(excel_path) if x.endswith(".xls")]
    startTime = time.time()

    p_list = []
    # 使用进程池Pool
    pool = Pool(processes=10)
    pool_data_list = []
    data = pd.DataFrame()
    for file_name in xls_names:
        # 需要注意不能直接在这里调用get方法获取数据,原因是apply_async后面 get()等待线程运行结束才会下一个,这里多进程会变成阻塞执行
        pool_data_list.append(pool.apply_async(getExcelData, (os.path.join(excel_path, file_name),)))
    pool.close()
    # 需要阻塞以下,等所有子进程执行完毕后主线程才继续执行
    pool.join()
    for pool_data in pool_data_list:
        # 这里再使用get()方法可以获取返回值
        data = data.append(pool_data.get())
    endTime = time.time()
    print(endTime - startTime)
    print(len(data))

结果:

进程池最大数量设为10:6.580815315246582

进程池最大数量设置为120:20.111015796661377

可以看出,当进程数设置为120时,使用线程池仍然花费了20秒,与之前手动创建线程测试结果相同。可见系统创建线程会花费较多的时间,创建适当数量的线程才是明智之选。

单进程读取对照测试

# @datetime:6/27/0027
"desc"
__author__ = "[email protected]"
import os.path
import time
from service import logger
import pandas as pd

logger = logger.MyLogger("excelUtils").getLogger()


class ExcelReader:

    def __init__(self, path, file_suffix=".xls"):
        self.path = path
        self.file_suffix = file_suffix

    def getData(self):
        if not os.path.exists(self.path):
            raise FileNotFoundError()
        data = pd.DataFrame()
        if os.path.isdir(self.path):
            xls_names = [x for x in os.listdir(self.path) if x.endswith(self.file_suffix)]
            logger.info("开始")
            for xls_name in xls_names:
                df = pd.read_excel(os.path.join(self.path, xls_name), skiprows=1, skipfooter=1)
                data = data.append(df, sort=False)
            logger.info("读取Excel文件完毕,共读取" + str(xls_names.__len__()) + "个文件")
        return data


if __name__ == "__main__":
    start = time.time()
    reader = ExcelReader(os.path.join(os.getcwd(), "../excels"))
    data = reader.getData()
    end = time.time()
    print(end - start)
    print(len(data))

结果:

耗时:12.019948959350586

结论

可见,创建适当的多进程执行任务能够有效提高执行效率,而盲目的创建较多的线程可能会适得其反。实际情况是,所有的任务最终都是cpu去执行的,我们创建等同于cpu核心数的线程数应该是最高效的,我们可以通过multiprocessing模块的cpu_count()方法获取cpu核心数。当然在一些任务量很小的情况下,单线程可能会更有效,因为创建进程同样会花费不少的时间。系统创建进程的时间要大大高于创建线程。

你可能感兴趣的:(python,python)