Python多处理:全局对象未正确复制到子对象
几天前,我回答了一个关于并行读取tar文件的问题 这是问题的要点:Python多处理:全局对象未正确复制到子对象,python,concurrency,parallel-processing,multiprocessing,python-multiprocessing,Python,Concurrency,Parallel Processing,Multiprocessing,Python Multiprocessing,几天前,我回答了一个关于并行读取tar文件的问题 这是问题的要点: import bz2 import tarfile from multiprocessing import Pool tr = tarfile.open('data.tar') def clean_file(tar_file_entry): if '.bz2' not in str(tar_file_entry): return with tr.extractfile(tar_file_ent
import bz2
import tarfile
from multiprocessing import Pool
tr = tarfile.open('data.tar')
def clean_file(tar_file_entry):
if '.bz2' not in str(tar_file_entry):
return
with tr.extractfile(tar_file_entry) as bz2_file:
with bz2.open(bz2_file, "rt") as bzinput:
# Reading bz2 file
....
....
def process_serial():
members = tr.getmembers()
processed_files = []
for i, member in enumerate(members):
processed_files.append(clean_file(member))
print(f'done {i}/{len(members)}')
def process_parallel():
members = tr.getmembers()
with Pool() as pool:
processed_files = pool.map(clean_file, members)
print(processed_files)
def main():
process_serial() # No error
process_parallel() # Error
if __name__ == '__main__':
main()
我们可以通过在子进程中而不是在父进程中打开tar文件来消除错误,如中所述
我不明白为什么会这样
即使我们在父进程中打开tarfile,子进程也将获得一个新副本。
那么,为什么在子进程中打开tarfile会有明显的区别呢
这是否意味着在第一种情况下,子进程以某种方式改变了公共tarfile对象,并由于并发写入而导致内存损坏?FWIW,注释wrt
open
中关于文件句柄号的答案在类UNIX系统上实际上是不正确的
如果multiprocessing
使用fork()
(它在Linux和类似系统下使用,尽管我读到macOS上的fork存在问题),文件句柄和其他所有内容都会被愉快地复制到子进程中(通过“愉快地”我的意思是,它在许多边缘情况下(例如分叉线程)都很复杂,但对于文件句柄来说仍然可以正常工作)
以下对我来说很好:
导入多处理
此=打开(_文件__,'r')
def read_文件():
打印(len(this.read())
def main():
process=multiprocessing.process(目标=read\u文件)
process.start()
process.join()
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
main()
问题很可能是,tarfile
在读取时具有内部结构和/或缓冲,而且,如果同时尝试查找和读取同一归档文件的不同部分,您可能会遇到冲突。也就是说,我推测在这种情况下,使用线程池而不进行任何同步可能会遇到完全相同的问题
编辑:为了澄清,从Tar归档文件中提取文件很可能(我没有检查确切的细节)如下所述:(1)查找封装部件(文件)的偏移量,(2)读取封装文件的块,将块写入目标文件(或管道,或w/e),(3)重复(2)直到整个文件被提取
通过使用同一个文件句柄从并行进程尝试非同步的方式,可能会导致这些步骤的混合,即开始处理文件第2个将从文件第1条中寻找,而我们处于读取文件1的中间,等等
Edit2回答下面的评论:内存表示是为子进程重新分叉的,这是真的;但是在内核端管理的资源(如文件句柄和内核缓冲区)是共享的 举例说明:导入多处理
this=open(_文件_,'rb')
def read_文件(工作程序):
打印(工人,本。读取(80))
def main():
进程=[]
对于(1,2)中的数字:
进程.append(
Process(target=read\u文件,args=(number,))
对于过程中的过程:
process.start()
对于过程中的过程:
process.join()
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
main()
在Linux上运行此操作,我得到:
$ python3.8 test.py
1 b"import multiprocessing\n\nthis = open(__file__, 'rb')\n\n\ndef read_file(worker):\n "
2 b''
如果搜索和读取是独立的,这两个过程将打印相同的结果,但它们不是。因为这是一个小文件,Python选择缓冲少量数据(8kib),所以第一个进程读取EOF,第二个进程没有数据可读取(当然,除非它返回)。
open
创建一个绑定到进程的文件句柄。在类UNIX系统上,它只是一个数字。这个数字对其他过程来说并不相同。当我回答你最初的问题时,你可以找到一篇关于这个主题的有趣帖子,我发布了一些代码,展示了如何初始化池中的每个进程,以像上面尝试的那样打开tarfile,从而使池中的每个进程只打开一次tarfile,而不是为提取的每个成员打开tarfile。你试过运行代码吗?@Booboo我不是问这个问题的人。是我回答的。我试过你的答案,效果很好。事实上,你和我的答案基本上是一样的。@AnmolSinghJaggi我似乎忽略了这一点。我突然想到,正如OP应该指定在询问带有regex
标记的问题时使用的语言一样,OP应该指定在发布带有多处理
标记的问题时使用的平台。我之前的评论适用于使用spawn
的平台,如Windows。在我对原始问题的回答中,我还建议OP使用spawn
。但是tarfile的内存表示应该重新复制到每个子进程中;那么,一个搜索将如何干扰另一个搜索呢?你是指磁盘上的实际文件吗?在这种情况下,OSX(或任何现代操作系统)不保证多个进程同时读取单个文件吗?事实上,这就是为什么第二个程序没有错误@AnmolSinghJaggi看到更新的答案;我指的是磁盘上的实际源文件,它在竞争读取和查找操作的工作人员之间共享。在回答时,我确实假设选择fork()的Unix Python,我的回答在macOS上可能无效:您是对的。当我们使用fork方法时,文件偏移量以某种方式在进程之间共享。然而,在spawn下,我们得到2个完全不同的文件句柄。如果您在程序顶部编写multiprocessing.set\u start\u method('spawn')
,您将注意到不同的输出。