python多线程与多进程

python多线程与多进程

由于python多线程并不是并行执行,因此较适合与I/O密集型程序,多进程并行执行适用于CPU密集型程序;

多线程Threading

GIL锁

提起python多线程

就不得不提一下GIL(Global Interpreter Lock 全局解释器锁),这是目前占统治地位的python解释器CPython中为了保证数据安全所实现的一种锁。不管进程中有多少线程,只有拿到了GIL锁的线程才可以在CPU上运行,即时是多核处理器。对一个进程而言,不管有多少线程,任一时刻,只会有一个线程在执行。对于CPU密集型的线程,其效率不仅仅不高,反而有可能比较低。python多线程比较适用于IO密集型的程序。对于的确需要并行运行的程序,可以考虑多进程。

thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

线程同步与线程锁:

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。

考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。

那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。

线程优先级队列( Queue)

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

alphapose与多线程、队列

使用multiprocessing模块:有两个类,Queue(队列)和Process(进程)

import torch.multiprocessing as mp ####多进程 mp.Queue() 、mp.Process()
from multiprocessing import Process
from multiprocessing import Queue as pQueue

from threading import Thread ###单进程

from queue import Queue, LifoQueue ###单进程

deamon守护线程

多进程multiprocessing

Process

在multiprocessing中,每一个进程都用一个Process类来表示。

def worker(procnum, return_dict):
    """worker function"""
    print(str(procnum) + ' represent!')
    return_dict[procnum] = str(procnum)

for i in range(10):
    p = Process(target=worker, args=(i, return_dict))
    p.start()
    p.join()

全局变量在多个进程中不能共享

在子进程中修改全局变量对父进程中的全局变量没有影响。因为父进程在创建子进程时对全局变量做了一个备份,父进程中的全局变量与子进程的全局变量完全是不同的两个变量。全局变量在多个进程中不能共享

Pool

如果要启动大量的子进程,可以用进程池的方式批量创建子进程

Pool(8):创建多个进程,表示可以同时执行的进程数量。默认大小是CPU的核心数果。

pool.map()、pool.apply_async():实例化多个进程,前者map一次传递多个,后者传递单个

close():如果我们用的是进程池,在调用join()之前必须要先close(),并且在close()之后不能再继续往进程池添加新的进程

join():进程池对象调用join,会等待进程池中所有的子进程结束完毕再去结束父进程

# 保存多进程结果
return_dicts = {}
## 进程数量
pool = multiprocessing.Pool(2)
## 任务数量
for i in range(4):
	pool.apply_async(run, args=(i, ))
    return_dicts.update(return_dict.get())
# 禁止添加新任务
pool.close()
# 将子进程添加到主进程
pool.join()

进程间的通信

现在有这样一个需求:我们有两个进程,一个进程负责写(write)一个进程负责读(read)。当写的进程写完某部分以后要把数据交给读的进程进行使用
这时候我们就需要使用到了multiprocessing模块的Queue(队列):write()将写完的数据交给队列,再由队列交给read()

注意事项

多进程中,单进程报错在jupyter中无法显示。

多进程使用pickle将每次返回的结果get后进行保留会导致顺序执行,相当于单进程。

每一个视频分辨率不一致,需要调整dataloader

共享内存

multiprocess.Manager

Manager() 返回的管理器支持类型:list、dict、Namespace、Lock、RLock、Semaphore、BoundedSemaphore、Condition、Event、Queue、Value、Array 。

自定义进程类

自定义进程类,继承Process类,重写run方法就可以了

torch.multiprocessing

封装了multiprocessing模块。用于在相同数据的不同进程中共享视图。

一旦张量或者存储被移动到共享单元(见share_memory_()),它可以不需要任何其他复制操作的发送到其他的进程中。

这个API与原始模型完全兼容,为了让张量通过队列或者其他机制共享,移动到内存中,我们可以

由原来的import multiprocessing改为import torch.multiprocessing

由于API的相似性,我们没有记录这个软件包的大部分内容,我们建议您参考原始模块的非常好的文档。

posted @ 2022-05-23 21:44  killens  阅读(637)  评论(0)    收藏  举报