编程语言
首页 > 编程语言> > Python:Multiprocessing跑满多核——一种分配任务的写作模板

Python:Multiprocessing跑满多核——一种分配任务的写作模板

作者:互联网

目录

太长不看版

一、多进程和多线程的区分

二、导包

1.演示包

2.用途

(1)multiprocessing

(2)tqdm

(3)pandas

三、函数设计

1.函数

(1)正常情况

(2)多核函数设计

2.参数设置

(1)q

(2)start、end、step

四、运行

1.入口

(1)Process函数的参数问题

(2)两个for?

2.运行及效果

(1)正常跑测试

(2)六开测试

(3)十二核(逻辑处理器)拉满

3.可不可以把数字写得更大?

五、小结


太长不看版

在11400h的测试中,多核拉满的速度为单开的6.22倍,长时间跑任务降频后仍有4.87倍,充分利用了多核的性能,加速了日常的计算。

任务分配用到的是切片访问的思路,用[start:end:step]来分割任务,将任务平均分配到多个逻辑处理器上。同时用multiprocessing的Queue()来传输数据。

因为多开是同时运行多个python应用,因此必须使用程序主入口,并且用右键的run的形式。

———————————————————————————————————————————

首先能写这个的契机是11代的笔电i5终于yes了一回,6核12线程的11400h都支楞起来了,不过看着平时比较低的利用率,总感觉钱白花了。

碰巧有时候用python处理一些任务时,动不动十几二三十分钟,自己写的屎山经常一核拉满多核围观。所以想了想,学了multiprocessing,并且慢慢摸索了一个写作思路。

一、多进程和多线程的区分

这里有几个知识点要重点记录一下

单个CPU在任一时刻只能执行单个线程,只有多核CPU还能真正做到多个线程同时运行
一个进程包含多个线程,这些线程可以分布在多个CPU上
多核CPU同时运行的线程可以属于单个进程或不同进程
所以,在大多数编程语言中因为切换消耗的资源更少,多线程比多进程效率更高
坏消息,Python是个特例!

GIL锁
python始于1991年,创立初期对运算的要求不高,为了解决多线程共享内存的数据安全问题,引入了GIL锁,全称为Global Interpreter Lock,也就是全局解释器锁。

GIL规定,在一个进程中每次只能有一个线程在运行。这个GIL锁相当于是线程运行的资格证,某个线程想要运行,首先要获得GIL锁,然后遇到IO或者超时的时候释放GIL锁,给其余的线程去竞争,竞争成功的线程获得GIL锁得到下一次运行的机会。

正是因为有GIL的存在,python的多线程其实是假的,所以才有人说python的多线程非常鸡肋。但是虽然每个进程有一个GIL锁,进程和进程之前还是不受影响的。

GIL是个历史遗留问题,过去的版本迭代都是以GIL为基础来的,想要去除GIL还真不是一件容易的事,所以我们要做好和GIL长期面对的准备。

多进程 vs 多线程
那么是不是意味着python中就只能使用多进程去提高效率,多线程就要被淘汰了呢?

那也不是的。

这里分两种情况来讨论,CPU密集型操作和IO密集型操作。针对前者,大多数时间花在CPU运算上,所以希望CPU利用的越充分越好,这时候使用多进程是合适的,同时运行的进程数和CPU的核数相同;针对后者,大多数时间花在IO交互的等待上,此时一个CPU和多个CPU是没有太大差别的,反而是线程切换比进程切换要轻量得多,这时候使用多线程是合适的。

所以有了结论:

CPU密集型操作使用多进程比较合适,例如海量运算
IO密集型操作使用多线程比较合适,例如爬虫,文件处理,批量ssh操作服务器等等
————————————————
版权声明:本文为CSDN博主「T型人小付」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Victor2code/article/details/109005171

二、导包

1.演示包

import multiprocessing as mp
from tqdm import tqdm
import pandas as pd

2.用途

(1)multiprocessing

多进程的包用的是python内置的multiprocessing,区别于多线程用的threadings。

导完mp之后可以看一下自己的核心数,后面有用。

multiprocessing.cpu_count()

(2)tqdm

tqdm是一个进度条的包,可以有效治疗等待焦虑症。

正常来讲只要把它包在可迭代对象外面就行,不过笔者最近发现pandas的series不是tqdm可以包装的对象。下文中笔者对series按list强转了之后才可以。

(3)pandas

在本文中用pandas作演示,主要也是考虑到pandas的数据有index,即使不同核心之间处理任务速度不一样,导致分别写进queue的时候顺序是乱的,出来的时候也是有顺序的。

三、函数设计

1.函数

(1)正常情况

一个正常的函数如下

def main(data):
    tasks = ['姓名', '性别', '班级', '专业']    # 假设我们的任务是根据学号查询姓名、性别、班级等信息
    op = pd.DataFrame(columns=tasks)    # 在每个进程中都创建一张空表
    op['学号'] = data['学号']    # 只放入任务量的学号
    op.set_index('学号', inplace=True, drop=False)    # 将学号设置为索引的同时将其保留在表格中
    """
    在大表里面查每一个小任务
    """
    for i in tqdm(op['学号']):
        for j in tasks:
            op.loc[[i], [j]] = data[data['学号'] == i][j].values[0]

(2)多核函数设计

 给正常函数加上了q、start、end、step四个参数,构造成如下的样子

def main(data, q, start, end, step):
    tasks = ['姓名', '性别', '班级', '专业']    # 假设我们的任务是根据学号查询姓名、性别、班级等信息
    op = pd.DataFrame(columns=tasks)    # 在每个进程中都创建一张空表
    op['学号'] = data['学号'][start:end:step]    # 只放入任务量的学号
    op.set_index('学号', inplace=True, drop=False)    # 将学号设置为索引的同时将其保留在表格中
    """
    在大表里面查每一个小任务
    """
    for i in tqdm(op['学号']):
        for j in tasks:
            op.loc[[i], [j]] = data[data['学号'] == i][j].values[0]

    q.put(op)    # 把op丢进queue里,等下取出来

2.参数设置

(1)q

对应的是multiprocessing里的queue,.put()一端放进一个数据,.get()一端取出这个数据,先放进去的先取出来。在本文里面因为每个数据都有索引,如果多核之间速度不一样,也不会导致乱序的问题。

(2)start、end、step

没错,就是常见的切片操作,start放0,end放任务总数,step写核心数量,比如笔者的11400H就写12,任务就能被分配成12个核心平均处理的量。

四、运行

1.入口

多进程是同时开好几个python跑,所以写完函数不能直接引,得加点繁文缛节。

if __name__ == '__main__':
    dt = pd.read_excel(file, dtype=object)    # 用pandas读表
    qq = mp.Queue()    # 企鹅:? 好吧,这就是上文提到的管道,用来沟通多核的任务
    df_op = pd.DataFram()
    for i in range(12):    # 取决于核心数量
        p = mp.Process(taiget=main, args=(qq, dt, 0+i, 100, 12))
        p.start()
    for i in range(12)
        df_op = df_op.append(qq.get())
    
    df_op.to_excel(file1, encoding='utf_8_sig', index=False)

(1)Process函数的参数问题

对于target,这里写函数,但不写括号。

参数用args去传入!

start为0+i,end为总任务量。

(2)两个for?

先跑完p再用q

2.运行及效果

以下部分笔者用的是自己的另一个程序的数据。

(1)正常跑测试

此时单核速度9.16it/s

(2)六开测试

此时每个单核的速度是5.73it/s,多核的速度照此估算是5.73*6=34.38it/s

(3)十二核(逻辑处理器)拉满

12个,看看拉满的效果。

  

总速度是4.75*12=57it/s

但实际上因为温度控制的问题,时间久了之后CPU会降频,笔者用了各种办法,最后成绩稳定在3.8it/s左右

3.8*12=44.6it/s

3.可不可以把数字写得更大?

可以,不过同时开18个python还能150%不成?

 再写多一点也没啥用。

五、小结

多核处理中,处理速度和核心数不是线性关系,会有边际效应,但这个效应不明显。

以单开9.16it/s,六开34.38it/s和全开降频前57it/s、降频后44.6it/s的速度来看

六开的速度达到单开的3.75倍

全开的速度降频前达到单开的6.22倍,降频后达到4.87倍

对于有多核处理器的小伙伴来说,岂不美哉?

标签:多线程,Python,满多核,线程,分配任务,进程,GIL,CPU,op
来源: https://blog.csdn.net/HUST_Yerdon/article/details/122161783