>백엔드 개발 >파이썬 튜토리얼 >mutilprocessing에서 상위 프로세스와 하위 프로세스 간에 파일 객체를 공유할 때 주의할 사항 Python에서 처리

mutilprocessing에서 상위 프로세스와 하위 프로세스 간에 파일 객체를 공유할 때 주의할 사항 Python에서 처리

巴扎黑
巴扎黑원래의
2017-04-15 09:04:481740검색

위 코드의 목적은 매우 명확합니다. multiprocessing.Process를 사용하여 put 함수를 실행하는 프로세스를 파생시킵니다. put 함수의 함수도 매우 명확합니다. PUT 및 END를 출력하고 "hello, func put write"를 씁니다. "를 error1.log 파일에 추가합니다.

출력은 위에서 언급한 대로 PUT 및 END여야 하며 그런 다음 error1.log에 "hello, func put write"라는 문장이 있어야 합니다. 그러나 세상에는 항상 예측할 수 없는 일이 있기 마련인데, 코드 실행 결과는

from multiprocessing import Process, Lock
err_file = 'error1.log'  
err_fd = open(err_file, 'w')

def put(fd):
     print "PUT"
     fd.write("hello, func put write\n")
     print "END"

if __name__=='__main__':
    p_list=[]
    for i in range(1):
        p_list.append(Process(target=put, args=(err_fd,)))    
    for p in p_list:
        p.start()
    for p in p_list:
        p.join()
뭐! ? 왜 error1.log에 아무것도 없는 걸까요!?

코드를 조금 조정하여 다시 마법을 확인해보세요:

[root@iZ23pynfq19Z ~]# py27 2.py ; cat error1.log 
PUT
END
[root@iZ23pynfq19Z ~]#
출력 결과:

from multiprocessing import Process, Lock
err_file = 'error1.log'  
err_fd = open(err_file, 'w')

def put(fd):
     print "PUT"
     fd.write("hello, func put write\n")
     fd.write("o" * 4075) # 神奇的一行
     print "END"

if __name__=='__main__':
    p_list=[]
    for i in range(1):
        p_list.append(Process(target=put, args=(err_fd,)))    for p in p_list:
        p.start()
    for p in p_list:
        p.join()
혼란스러우신가요? ? 기분 좋아요!?

이제 마음속에 두 가지 질문이 떠오릅니다.

왜 첫 번째 프로그램은 그 문장을 쓸 수 없는데 두 번째 프로그램은 🎜>

    저 4075는 대체 뭐죠?
  1. 이러한 질문을 설명하기 전에 표준 IO 라이브러리의 특성을 명확히 해야 합니다.

    풀 버퍼링, 라인 버퍼링, 아니요 버퍼링


  2. 자세한 내용은 이전 블로그 게시물을 참조하세요: https://my.oschina.net/u/2291...왜냐하면 이제 파일에 쓰고 있으므로 시스템 IO가 완전히 버퍼링됩니다. 즉, 버퍼가 시스템 쓰기 대기열로 플러시되기 전에 채워집니다.

  3. 따라서 위의 문제는 한 번에 해결됩니다. , 그 신비한 'o' 때문에 전체 버퍼를 채우므로 시스템이 콘텐츠를 쓰기 대기열로 플러시합니다. 그러면 4075는 어떻게 나온 걸까요? 4096-sizeof("hello, func put writen")+1을 사용하면 됩니다. +1, 버퍼 때문에 영역이 가득 차면 쓰기 작업을 실행하려면 더 커야 합니다.

이제 비슷한 방식으로 파일을 쓰려면 답을 얻을 수 있습니다. multiprcessing.Process에는 세 가지 방법이 있습니다.

버퍼 채우기

    flush()를 수동으로 호출
  • 파일 호출 개체가 버퍼링되지 않도록 설정
  • 첫 번째와 두 번째 유형은 위에서 설명했으니 세 번째 유형에 대해 간단히 이야기해 보겠습니다.

[root@iZ23pynfq19Z ~]# py27 2.py ; cat error1.log 
PUT
END
hello, func put write
o....(有4075个)
[root@iZ23pynfq19Z ~]#
  • 위 그림은 버퍼링을 0으로 설정하면 버퍼링되지 않은 모드입니다. 그런 다음 쓸 때마다 버퍼에 쓰지 않고 쓰기 대기열에 직접 기록됩니다. (가장 낮은 성능 방법)


    ------ ------------------- -------------선을 자르고 있습니다---- ---------------- -------------

  • 이야기를 마쳤습니다.

    파일 객체가 있을 때 시도해 본 적이 있을 것 같습니다. 명시적으로 닫히지 않거나 플러시가 명시적으로 호출되면 파일은 여전히 ​​정상적으로 작성될 수 있는데 무슨 일이 일어난 걸까요?

    사실 프로그램을 정상적으로 닫으면 프로세스가 일부 "손- 열려 있는 파일 설명자 닫기, 임시 파일 정리, 메모리 정리 등과 같은 작업이 종료 후에 우리를 위해 "on"으로 설정됩니다. 정확히 시스템의 이러한 "좋은 습관" 때문에 데이터가 쓰기 대기열로 플러시될 수 있습니다. 파일 디스크립터는 닫혀 있고 파일 내용은 손실되지 않습니다.

    그러므로 이러한 이해를 바탕으로 다시 살펴보겠습니다. 지금 문제는 자식 프로세스가 put을 호출할 때 이론적으로 파일 디스크립터가 그렇지 않다는 것입니다. 프로그램이 종료되면 닫히므로 버퍼에 있는 데이터가 손실됩니다.

    단서를 따라가서 Process Implement

    取自Python官网Document:
    open(name[, mode[, buffering]])
      ...
      The optional buffering argument specifies the file’s desired buffer size: 0 means unbuffered, 
      1 means line buffered, any other positive value means use a buffer of (approximately) that 
      size (in bytes). A negative buffering means to use the system default, which is usually line 
      buffered for tty devices and fully buffered for other files. If omitted, the system default is 
      used. [2]

    Popn이 어떻게 작동하는지 살펴보겠습니다.

    multiprocessing/Processing.py
        def start(self):
            '''
            Start child process
            '''
            assert self._popen is None, 'cannot start a process twice'
            assert self._parent_pid == os.getpid(), \
                   'can only start a process object created by current process'
            assert not _current_process._daemonic, \
                   'daemonic processes are not allowed to have children'
            _cleanup()
            if self._Popen is not None:
                Popen = self._Popen
            else:
                from .forking import Popen
            self._popen = Popen(self)
            _current_process._children.add(self)

    핵심은 마지막 os._exit(코드)입니다. 왜 이 부분의 종료가 처리할 "손꼬리"가 무엇인지 결정하기 때문입니다. os._exit가 대체 뭐죠? 사실 표준 라이브러리의 _eixt이므로 간단히 다시 배울 수 있습니다

    https://my.oschina .net/u/2291...

    위 링크에서 _exit()와 exit()가 서로 다른 두 가지임을 분명히 알 수 있습니다. _exit()는 단순한 폭력이며 사용자 모드 콘텐츠가 커널에 직접 들어가고 종료()가 정리됩니다. 인내심을 갖고 진행해 보세요

    그러면 다음과 같이 가정할 수 있습니다. Popen의 종료가 os._exit()가 아닌 경우 어떤 효과가 있을까요?

    다행히도 sys.exit()는 종료()입니다. 먼저 필요합니다. 더 이상 고민하지 말고 시도해 보세요!

    multiprocessing/forking.py
        class Popen(object):
    
            def __init__(self, process_obj):
                sys.stdout.flush()
                sys.stderr.flush()
                self.returncode = None
    
                self.pid = os.fork()
                if self.pid == 0:
                    if 'random' in sys.modules:
                        import random
                        random.seed()
                    code = process_obj._bootstrap()
                    sys.stdout.flush()
                    sys.stderr.flush()
                    os._exit(code)

    코드를 테스트하고 'o' 패딩 버전 없이 원본 코드를 반환합니다

    multiprocessing/forking.py
        class Popen(object):
    
            def __init__(self, process_obj):
                sys.stdout.flush()
                sys.stderr.flush()
                self.returncode = None
    
                self.pid = os.fork()
                if self.pid == 0:
                    if 'random' in sys.modules:
                        import random
                        random.seed()
                    code = process_obj._bootstrap()
                    sys.stdout.flush()
                    sys.stderr.flush()
                    #os._exit(code)
                    sys.exit(code)

    실제로 코드를 작성할 수 있음을 알 수 있습니다. , 이는 위의 진술이 타당함을 증명합니다

    그러나 소스 코드를 무작위로 변경하지 않는 것이 가장 좋습니다. 결국 이것들은 모두 선배들이 수년간 최적화한 결과일 수도 있습니다. 특정 문제를 피하기 위해서는 자신의 행동을 표준화하고 표준화되지 않은 것처럼 보이는 구현 아이디어를 줄이는 것이 좋습니다.

    모두 환영합니다. 전문가의 조언과 의사소통이 필요하면 재인쇄할 때 표시해 주세요: https://segmentfault.com /a/11...

    multiprocessing python 다중 프로세스 모듈이므로, 프로세싱도 다중 프로세스의 사랑입니다. 하지만 오늘 논의된 문제는 다음과 같습니다.
    코드로 바로 이동해 보겠습니다.

    [root@iZ23pynfq19Z ~]# python 2.py ; cat error1.log 
    PUT
    END
    hello, func put write
    위 코드의 의도는 매우 명확합니다. 다중 처리를 통해 프로세스를 파생시키고 put 함수를 실행합니다. put 함수의 역할도 매우 명확합니다. PUT과 END를 출력하고 error1.log 파일에 "hello, func put write"를 씁니다.

    那么按理说, 输出应该如同上面说的那样, PUT和END,然后error1.log将有那句话"hello, func put write", 然而, 世事总有那么点难料的, 代码执行结果是:

    [root@iZ23pynfq19Z ~]# py27 2.py ; cat error1.log 
    PUT
    END
    [root@iZ23pynfq19Z ~]#

    what!? 为什么error1.log没东西 !?

    让我们稍微调整下代码, 再见证神奇的事情:

    from multiprocessing import Process, Lock
    err_file = 'error1.log'  
    err_fd = open(err_file, 'w')
    
    def put(fd):
         print "PUT"
         fd.write("hello, func put write\n")
         fd.write("o" * 4075) # 神奇的一行
         print "END"
    
    if __name__=='__main__':
        p_list=[]
        for i in range(1):
            p_list.append(Process(target=put, args=(err_fd,)))    for p in p_list:
            p.start()
        for p in p_list:
            p.join()

    输出结果:

    [root@iZ23pynfq19Z ~]# py27 2.py ; cat error1.log 
    PUT
    END
    hello, func put write
    o....(有4075个)
    [root@iZ23pynfq19Z ~]#

    有没有觉得一种懵逼的感觉!?

    如今, 心中涌现两个问题:

    1. 为什么第一个程序无法写入那句话 , 但是第二个却可以?

    2. 那个4075是什么鬼?
      在解释这些问题之前, 我们需要清楚标准IO库所具有的特点: 全缓冲, 行缓冲, 不缓冲

    具体可以看之前博文:https://my.oschina.net/u/2291...

    因为现在是写入文件, 所以系统IO将采用全缓冲的方式, 也就是说, 会将缓冲区填满才刷入系统写队列.

    所以上面的问题就一下子全解决了, 正因为那些 迷一般的 'o',填满了整个缓冲区, 所以系统将我们的内容刷进去写队列,所以4075怎么来, 就是用4096-sizeof("hello, func put writen")+1, 为什么要+1, 因为缓冲区满还不行, 要大于才能触发写动作.

    所以我们现在已经能够得出答案, 如果我们想要在multiprcessing.Process中, 用上面类似的方式去写文件时,有三种方法去实现:

    • 写满缓冲区

    • 手动调用flush()

    • 将文件对象设置成不缓冲
      第一第二种在上面已经阐述, 那我们简单讲下第三种:

    取自Python官网Document:
    open(name[, mode[, buffering]])
      ...
      The optional buffering argument specifies the file’s desired buffer size: 0 means unbuffered, 
      1 means line buffered, any other positive value means use a buffer of (approximately) that 
      size (in bytes). A negative buffering means to use the system default, which is usually line 
      buffered for tty devices and fully buffered for other files. If omitted, the system default is 
      used. [2]

    上图说明就是, 允许我们在open的时候, 设置buffering为0, 那么就是unbuffered模式, 那么在每次写, 就是直接写入写队列,而不是写到缓冲区.(性能最低的方式)

    ------------------------------------------------我是切割线----------------------------------------------

    谈论完现象和处理的方法, 我们应该来点深入的;

    相信我们曾经试过, 在没有显示关闭文件对象或者显示调用flush时, 文件依旧能够正常写入,那么又是怎么一回事呢?

    其实,在我们正常关闭程序时, 进程在退出将会为我们做一些"手尾", 例如关闭打开的文件描述符, 清理临时文件,清理内存等等.正是因为系统的这种"好习惯", 所以我们的数据在文件描述符关闭时,就能刷入写队列,文件内容也不会丢失.

    那么基于这种认识,我们再回首刚才的问题, 在子进程调用put的时候, 理论上在程序退出时, 并没显示关闭文件描述符, 所以数据在缓冲区就丢失了.

    让我们在顺藤摸瓜,看Process的实现

    multiprocessing/Processing.py
        def start(self):
            '''
            Start child process
            '''
            assert self._popen is None, 'cannot start a process twice'
            assert self._parent_pid == os.getpid(), \
                   'can only start a process object created by current process'
            assert not _current_process._daemonic, \
                   'daemonic processes are not allowed to have children'
            _cleanup()
            if self._Popen is not None:
                Popen = self._Popen
            else:
                from .forking import Popen
            self._popen = Popen(self)
            _current_process._children.add(self)

    再看下Popn是怎么做?

    multiprocessing/forking.py
        class Popen(object):
    
            def __init__(self, process_obj):
                sys.stdout.flush()
                sys.stderr.flush()
                self.returncode = None
    
                self.pid = os.fork()
                if self.pid == 0:
                    if 'random' in sys.modules:
                        import random
                        random.seed()
                    code = process_obj._bootstrap()
                    sys.stdout.flush()
                    sys.stderr.flush()
                    os._exit(code)

    关键地方就是最后的 os._exit(code), 为什么说最关键? 因为这部分的退出, 将决定进程会处理什么"手尾",

    os._exit是什么鬼?  其实就是标准库的_eixt, 于是我们又能简单学习这东西了

    https://my.oschina.net/u/2291...

    在上面的链接, 我们能够比较清楚看到 _exit() 和exit() 是比较不同的两个东西, _exit()  简单暴力, 直接丢弃用户态的内容,进入内核, 而exit()则比较耐心地为我们清理

    那么我们是否能够假设: 如果Popen的退出不是os._exit() 会是怎样的效果呢?

    很幸运的是, sys.exit() 就是我们先要的exit(), 事不宜迟, 赶紧试下!

    multiprocessing/forking.py
        class Popen(object):
    
            def __init__(self, process_obj):
                sys.stdout.flush()
                sys.stderr.flush()
                self.returncode = None
    
                self.pid = os.fork()
                if self.pid == 0:
                    if 'random' in sys.modules:
                        import random
                        random.seed()
                    code = process_obj._bootstrap()
                    sys.stdout.flush()
                    sys.stderr.flush()
                    #os._exit(code)
                    sys.exit(code)

    测试代码, 返回最原始那个没有'o'填充的版本

    [root@iZ23pynfq19Z ~]# python 2.py ; cat error1.log 
    PUT
    END
    hello, func put write

    我们可以看到, 确实是可以写进去, 这样就证明上面的说法是站得住脚步的

    不过最好还是不要乱改源码哦, 毕竟这些都是老前辈多年优化的结果,可能这是他们故意这些写,为了避免某些问题.还是规范好自己的行为,尽量减少这些看起来不怎么规范的实现思路吧


    위 내용은 mutilprocessing에서 상위 프로세스와 하위 프로세스 간에 파일 객체를 공유할 때 주의할 사항 Python에서 처리의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.