Python使用multiprocessing进行多线程和多进程操作

  本文将介绍使用multiprocessing进行多线程和多进程操作。

多线程

  如果我需要对一个列表的每一个元素都要执行一个函数操作,并且每个元素执行的操作互不影响,那我们可以让列表里的所有元素在同时进行执行,而不是使用for循环让其一个一个执行,这种操作常常在独写文件中处理,比如我们要下载一系列的图片,数量很多如果一个一个下载,就会让很多cpu资源闲置,这时候我们就可以使用多线程及进行。

  我们通过multiprocessing.pool.ThreadPool类创造一个线程池,可以指定内部线程个数(同时对列表的多少元素同时进行)。之后使用map方法,或者apply_async方法对数据进行异步同时对多个元素操作,下面直接看例子。

例子一:map方法

from multiprocessing.pool import ThreadPool
import time

def func(p):
    print(p)
    time.sleep(3)

tic = time.time()
my_task = [1,2,3,4,5,6,7,8,9,10]
pool = ThreadPool(5)
pool.map(func, my_task)  # 把任务放入线程池,进行
toc = time.time()

print('costs: {}s'.format(toc - tic))
1
2
3
4
5
6
7
8
9
10
costs: 6.009862422943115s

  可以根据执行时间看到,首先是对前5个元素进行了函数func操作,等待了3秒后再对后面5个元素进行操作,完成了并行运算。不过我们要注意的是map方法的函数func只能接受1个传参,所以如果你有多个参数需要传入,那map方式就不适合了(当然你可以通过传入一个元组然后在内部进行读取也可以实现传出“多个”参数的目的)。还有提醒一点的是,如果线程没有结束,那整个程序是不会结束的(称为父进程堵塞)

方式二:apply_async方法

from multiprocessing.pool import ThreadPool
import time

def func(p,s):
    print(p,s)
    time.sleep(3)

tic = time.time()
my_task = [1,2,3,4,5,6,7,8,9,10]
pool = ThreadPool(5)
for item in my_task:
    pool.apply_async(func, args=(item,"可以"))  # 把任务放入线程池,进行
toc = time.time()

pool.close()  # 关闭线程池
pool.join()   # 等待所有任务完成

print('costs: {}s'.format(toc - tic))

1 可以
2 可以
4 可以
5 可以
3 可以
6 可以
7 可以
9 可以
8 可以
10 可以
costs: 0.008276700973510742s

  大家可能会疑惑,为啥是打印的costs:0.00827呢?其实这就是apply_async方法相对于map方法的差别了,刚说到map方法是会阻塞父进程的,换句话说只有当你的元素都执行完之后,才会继续执行pool.map后面的语句,而pool.apply_async则只需要把任务放进去就会立刻开始执行之后的命令,不需要等待放入线程池的任务执行完成,换句话说,这里的costs:0.00294只是刻画的for循环的时间,并不是我的元素在func中只想完成的时间,这称为不阻塞父进程。这个方法的优势就是func是可以有多个传参的。

  但是为什么costs的打印却等到了任务完成才打印的呢?因为虽然apply_async方法不阻塞进程,但是pool.join()是阻塞进程的,因为.join()方法是等待pool池中的所有任务结束,因此只有结束后,才会进入print。(.close()和.join()方法的介绍可以看使用经验一

  以上是multiprocessing.pool进行多线程操作的简单运用,推荐是使用多线程进行IO操作,互不影响。

 多进程

   在一些模块的调用上需要开启新的进程才能工作,例如使用ffmpeg这些包来抽取图片单帧信息,这个包只能在一个进程中使用,因此如果你把这个抽去任务放到线程池中其实也就是在一个一个抽取,因此在一些任务上我们需要采用多进程的方式进行,我们就可以利用multiprocessing中的Pool模块(注意这里是大写)。以下是模板,其实和线程池的操作一样这里采用的是map函数(具体见上文)

from multiprocessing import Pool
def func(item):
    pass
pool = Pool(processes=8) # 设置进程池的大小
pool.map(func, load_dict) # 其中load_dict是一个列表或者可迭代对象
pool.close()
pool.join()

  但是要注意这样代表着开启了新的进程,那意味着每个进程之间是无法通信的,也就是看不到对方的东西的,因此进程池一般用于储存等操作。如果非要想进程间进行通讯,例如我想用一个列表储存一下无法被读取的文件名,则可以使用multiprocessing中的Manager模块。使得各个进程中都能看到这个List,代码模板。

from multiprocessing import Pool, Manager

manager = Manager()
transition_label_list = manager.list() # 注册一个列表可以被所有进程看到的列表

def func(mp4):
    transition_label_list.append(...)
    pass

pool = Pool(processes=8)
pool.map(func, load_dict)
pool.close()
pool.join()
print('多进程完成')

  这里要注意到,如果你要把transition_label_list储存到本地,最好是先使用list(transition_label_list)将其转化为列表(transition_label_list此时的类型并不是List而是一个Manager的一个类)之后再储存。

 使用经验一:

  在上面的代码中出现了pool.close()和pool.join(),下面我们详细讲解下这两个函数的作用是什么。

  • pool.close():该方法会阻止线程池继续接受新的任务,此时该pool对象将不能再调用apply_async等加入新的任务了。相当于终结了这个线程/进程池的再次使用。
  • pool.join():该方法会等待所有的子进程完成任务后再继续执行主进程(此时会阻塞主进程),但不会关闭进程池。
  因此,在上面的两个异步进程池/线程池例子中,都是先调用了.close()不再允许新的任务加入到这个池子中,在调用pool.join()阻塞主线程,直到pool池子中的任务全部完成,释放资源。因此异步多线程的最好使用方式,就是使用threading.Thread创建一个新线程,在线程中创建一个新的pool = ThreadPool(4), 然后在这个线程中使用异步pool.apply_async()添加任务,最后在这个线程中pool.close()和pool.join()让这个线程的任务结束。

 

 

参考网站:

python3的multiprocessing多进程-Pool进程池模块 - 整合侠 - 博客园 (cnblogs.com)

Python 多进程之间共享变量 - 知乎 (zhihu.com)

【python】详解multiprocessing多进程-Pool进程池模块(二)_brucewong0516的博客-CSDN博客_python多进程pool

 

posted @ 2021-12-18 17:01  Circle_Wang  阅读(7696)  评论(0编辑  收藏  举报