cmd查看进程号_python多进程写入同一文件

cmd查看进程号_python多进程写入同一文件0.前言此文诞生源于python多进程的一个诡异表现,如果你使用pytho多进程过程中,发现子进程被挂起(一直处于Sleeping),请参考本文档

0.前言

此文诞生源于python多进程的一个诡异表现,如果你使用pytho多进程过程中,发现子进程被挂起(一直处于Sleeping),请参考本文档。

本文先简单介绍python多进程常规用法,然后主要说说这个bug的根源,以及如何解决。

1.python多进程使用

python多进程库是multiprocessing,一般我们常用它的Pool,怎么用直接看示例代码。

ps : 这里只是简单来一个示例,因为默认大家了解python基本的多进程使用,想了解其他的请自行google

# multiprocessing.Pool 示例
def subprocesses(argv):
    """do Subprocesses job"""
    print(f"argv = {argv}")
    sleep(1)
    return argv

if __name__ == '__main__':
	pool = multiprocessing.Pool(processes=4)
    results = []
    for i in xrange(10):
        msg = "hello %d" %(i)
        sub_res = pool.apply_async(func, (msg, ))
        results.append(sub_res)
    pool.close() # 关闭进程池,表示不能再往进程池中添加进程,需要在join之前调用
    pool.join() # 等待进程池中的所有进程执行完毕
    print ("Sub-process(es) done.")

    for res in results:
      	# get函数获取子进程返回结果 
        print (res.get())

2.python多进程中子进程hangs问题

先看一段代码

import torch
import torch.multiprocessing as mp


def foo():
    x = torch.ones((2, 50, 10))
    return torch.einsum('ijl,ikl->ijk', x, x)


if __name__ == '__main__':
    foo()
    p = mp.Process(target=foo)
    p.start()
    p.join()

这段代码一直不会结束,子进程会被挂起。原因是torch.einsum运算使用了OpenMP。类似的操作还有numpy.dottorch.matmul等等各种矩阵运算。

2.1 哪些情况下子进程可能挂起

这个问题就很难回答了,跟硬件,操作系统,线程数,矩阵规模都有关系,是一个由来已久的问题。
摘抄numpy #5752一个人的回答如下:
“By default, Python multiprocessing does fork without exec which breaks various libraries that use posix thread pools (or other) internally (Accelerate, CUDA, libgomp the OpenMP implementation of gcc). This is probably still the case under some circumstances (e.g. data size) for Apple Accelerate although it seems to depend on the versions. The first time we observed the issue was on OSX 10.7. We (@cournape and I) reported the bug and Apple replied: wontfix, fork without exec is a POSIX standard violation (which is true BTW).”

总结一下可能的情况:

  • 你使用了某个版本的apple accelerate
  • 某些情况下,data size过大也会导致这个现象
  • 主进程和子进程没有按照OpenMP规范使用(详细看下面的2.2)
  • 你在主进程初始化cuda,然后调用fork(),并在子进程中也是用cuda

第一种是因为apple accelerate不支持fork模式的多进程,这是apple的问题,但是他们并不认为这是一个bug…
第二种某些条件下,没有规律的触发条件,见一个debug一个吧…
第三种会在下面2.2中具体说
第四种和第三种形式上有点相似,如果没看明白,看了三再读一遍就理解了。

2.2 OpenMP是什么

一句话说: “OpenMP (as of version 4.5), a simple C/C++/Fortran compiler extension that allows to add parallelism into existing source code without significantly having to rewrite it”

这是一个c/c++/Fortran等语言编译器的一个扩展,使得你不用写多线程代码,可以直接在原来代码上加上一行编译器看的懂得注释,编译器就会自动帮你多线程运行一些耗cpu的操作。在GCC中,它叫做libgomp

举个例子,直接从官网截图的

c7e97e8aefe18fa5e7870b6e76237c48.png

如你所见,#pragma就是编译器看的懂得“注释”,在GCC中线程的数量是the number of processors

OpenMP的好处是显而易见的:

  • 方便:不用修改原来的代码,只需要添加一行类似的注释的东西
  • 安全:对于不支持OpenMP的编译器,会忽略该行,所以很安全

但是这玩意也是有缺点的,简单来说就是当你使用fork()时,如果父进程和子进程同时使用OpenMP,且父进程先使用OpenMP再调用fork(),则会造成子进程挂起!

举个官网的例子:

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

  void a()
  {
    
    
    #pragma omp parallel num_threads(2)
    {
    
    
      puts("para_a"); // output twice
    }
    puts("a ended"); // output once
  }
  void b()
  {
    
    
    #pragma omp parallel num_threads(2)
    {
    
    
      puts("para_b");
    }
    puts("b ended");
  }

  int main() {
    
    
   a();   // Invokes OpenMP features (parent process)
   int p = fork();
   if(!p)
   {
    
    
     b(); // ERROR: Uses OpenMP again, but in child process
     _exit(0);
   }
   wait(NULL);
   return 0;
  }

如上代码,父进程调用的函数a()中先使用了OpenMP,然后调用了fork(),那么子进程中b()中的”b ended”永远不会执行。

而且这玩意还无法解决….官网这么说”There is currently no workaround; the libgomp API does not specify functions that can be used to prepare for a call to fork().”

OK简单介绍这么多,有兴趣自己看这个:https://bisqwit.iki.fi/story/howto/openmp/#OpenmpAndFork

2.3 如何解决

总结如下几种尝试方案:

  • 尝试使用spawn或者forkserver模式
    multiprocessing 支持三种方式启动进程:
    • spawn: 父进程启动一个新的python解释器进程,子进程只继承运行 Run()方法所需的资源。来自父进程的不必要的文件描述符和句柄将不会被继承,运行速度比较慢,支持unix and windows
    • fork: 父进程使用os.fork()方法对Python解释器进行fork。子进程开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全的fork多线程的进程是有问题的。unix
    • forkserver: 当程序启动并选择forkserver的启动方法时,将启动服务器进程。从那时起,每当需要一个新进程时,父进程就会连接到服务器,并请求它fork一个新进程。fork server进程是单线程的,所以使用os.fork()是安全的。没有任何不必要的资源被继承。unix

注意:其中Unix默认使用fork模式, windows 默认使用spawn。
修改多进程mode使用例子:

import multiprocessing as mp
mp.set_start_method("spawn")  # 使用spqwn模式
# mp.set_start_method("forkserver")   # 使用forkserver模式

"""
这里执行多进程代码
"""

  • torch.set_num_threads(1)
    在父进程中创建子进程之前,执行torch.set_num_threads(1),并在子进程一开始也执行torch.set_num_threads(1)
    这种方式能解决问题,但是是讨巧的方式,其实它是限制了OpenMP在进程中使用多线程加速,属于magic方法,不提倡。但是有一个可能改进:”One possible improvement is to register a pthread_atfork handler that calls omp_set_num_threads(1) in the prepare and restores the value in parent and possibly child.”
  • 使用基于OpenBLAS的库
    OpenBLAS这个库解决了这个问题, 具体原因没有深究

参考资料:

  • https://bisqwit.iki.fi/story/howto/openmp/#OpenmpAndFork
  • https://github.com/pytorch/pytorch/issues/17199#issue-411060513
  • https://github.com/numpy/numpy/issues/5752#issuecomment-90752612
  • https://stackoverflow.com/a/24211633/5432806
  • https://github.com/numpy/numpy/issues/654#issuecomment-32126389
  • https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52303

今天的文章cmd查看进程号_python多进程写入同一文件分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/89239.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注