Heim  >  Artikel  >  Backend-Entwicklung  >  Dinge, die beim Teilen von Dateiobjekten zwischen übergeordneten und untergeordneten Prozessen bei der Mutilprocessing-Verarbeitung in Python zu beachten sind

Dinge, die beim Teilen von Dateiobjekten zwischen übergeordneten und untergeordneten Prozessen bei der Mutilprocessing-Verarbeitung in Python zu beachten sind

巴扎黑
巴扎黑Original
2017-04-15 09:04:481687Durchsuche

Multiprocessing-Python-Multiprozessmodul, daher ist die Verarbeitung auch der Liebling von Multiprozess. Aber die heute diskutierten Probleme scheinen unsere Aufmerksamkeit zu erregen

Gehen Sie direkt zum Code:

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()

Der Zweck des obigen Codes ist sehr klar: Verwenden Sie multiprocessing.Process, um einen Prozess zum Ausführen der Put-Funktion abzuleiten. Sie gibt PUT und END aus und schreibt „Hallo, Funktion“. put write“ in die Datei error1.log. .

Dann liegt es auf der Hand, dass die Ausgabe wie oben erwähnt sein sollte, PUT und END, und dann wird error1.log den Satz „Hallo, func put write“ enthalten ". Allerdings sind die Dinge immer etwas unvorhersehbar. Das Ergebnis der Codeausführung ist:

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

Was! ? Warum gibt es nichts in error1.log!?

Lassen Sie uns den Code ein wenig anpassen und die Magie noch einmal erleben:

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()

Ausgabeergebnis:

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

Gibt es ein Gefühl der Verwirrung!?

Jetzt tauchen in meinem Kopf zwei Fragen auf:

  1. Warum kann das erste Programm diesen Satz nicht schreiben, das zweite jedoch? Aber es funktioniert?

  2. Was zum Teufel ist das 4075?
    Bevor wir diese Fragen erklären, müssen wir uns über die Eigenschaften der Standard-IO-Bibliothek im Klaren sein: Vollständige Pufferung , Zeilenpufferung, keine Pufferung

Einzelheiten finden Sie im vorherigen Blogbeitrag: https://my.oschina.net/u/2291...

Da es jetzt in die Datei geschrieben wird, übernimmt die System-E/A eine vollständig gepufferte Methode, das heißt, der Puffer wird aufgefüllt, bevor er in die Schreibwarteschlange des Systems geleert wird.

Also Die oben genannten Probleme werden sofort gelöst, da das mysteriöse „o“ den gesamten Puffer füllt, sodass das System unseren Inhalt in die Schreibwarteschlange spült. Wie kommt es also, dass 4075 verwendet wird? 4096-sizeof("Hallo, func put writen")+1, warum brauchen wir + 1. Da der Puffer nicht voll ist, muss er größer sein, um die Schreibaktion auszulösen.

So können wir jetzt die Antwort bekommen. Wenn wir wollen Schreiben Sie Dateien auf ähnliche Weise wie oben in multiprcessing.Process. Es gibt drei Möglichkeiten, dies zu erreichen:

  • Voller Puffer

  • Manuell aufrufen Flush()

  • Setzen Sie das Dateiobjekt so ein, dass es nicht puffert
    Die erste und zweite Option wurden oben erläutert, also lassen Sie uns kurz über die dritte Option sprechen:

取自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]

Das obige Bild zeigt, dass wir die Pufferung im geöffneten Zustand auf 0 setzen können, was bedeutet, dass es sich um einen ungepufferten Modus handelt. Dann schreiben wir jedes Mal, wenn wir schreiben, direkt in die Schreibwarteschlange, anstatt in sie zu schreiben der Puffer. (Die Methode mit der niedrigsten Leistung)

----------------------------------- -------------Ich schneide Linie--------------------------------- --------------

Nachdem wir über das Phänomen und die Behandlungsmethoden gesprochen haben, sollten wir tiefer gehen;

Ich glaube, wir haben es schon einmal versucht Wenn das Dateiobjekt nicht explizit geschlossen oder Flush explizit aufgerufen wird, kann die Datei weiterhin normal geschrieben werden.

Tatsächlich führt der Prozess einige „Hand-“ aus, wenn wir das Programm normal schließen. on“ für uns nach dem Beenden, z. B. das Schließen offener Dateideskriptoren und das Bereinigen temporärer Dateien. Bereinigen des Speichers usw. Genau aufgrund dieser „guten Angewohnheit“ des Systems können unsere Daten in die Schreibwarteschlange geleert werden, wenn die Der Dateideskriptor ist geschlossen und der Dateiinhalt geht nicht verloren.

Lassen Sie uns auf der Grundlage dieses Verständnisses noch einmal auf das Problem zurückblicken, wenn der untergeordnete Prozess theoretisch den Dateideskriptor aufruft nicht geschlossen, wenn das Programm beendet wird, sodass die Daten im Puffer verloren gehen

Nachdem wir den Hinweisen gefolgt sind, schauen wir uns die Implementierung von 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)

an und schauen uns dann an, wie Popn funktioniert es?

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)

Der entscheidende Punkt ist der letzte os._exit(code), weil der Ausgang dieses Teils bestimmt, welche „Hände“ der Prozess behandeln wird mit,

Was zum Teufel ist os._exit? Es ist eigentlich _eixt der Standardbibliothek, also können wir dieses Ding einfach wieder lernen

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

Im obigen Link können wir deutlich sehen, dass _exit() und exit() zwei verschiedene Dinge sind. _exit() ist einfach und gewalttätig, indem es den Inhalt des Benutzermodus direkt verwirft und eingibt den Kernel, während exit() geduldiger für uns aufräumt

Dann können wir davon ausgehen: Wenn Popens Exit nicht os._exit( ) ist, was wird der Effekt sein?

Glücklicherweise hat sys. „exit()“ ist das „exit()“, das wir zuerst benötigen. Versuchen wir es schnell!

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)

Testen Sie den Code und geben Sie die Originalversion ohne „o“-Auffüllung zurück

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

Wir können sehen, dass es tatsächlich geschrieben werden kann, was beweist, dass die obige Aussage haltbar ist

Aber es ist am besten, den Quellcode nicht zufällig zu ändern. Dies sind schließlich die Ergebnisse langjähriger Optimierung von Senioren. Vielleicht ist es besser, Ihr Verhalten zu regulieren und zu versuchen, die Umsetzungsideen zu standardisieren. Bitte begrüßen Sie alle Experten Geben Sie Folgendes an: https://segmentfault.com/a/11...


Multiprocessing-Python-Multiprozessmodul, daher ist Processing auch der Liebling von Multi -Prozess. Aber die heute besprochenen Probleme scheinen unsere Aufmerksamkeit zu erregen

Gehen Sie direkt zum Code:

Der Zweck des obigen Codes ist sehr klar: Verwenden Sie Multiprocessing.Process zum Ableiten Ein Prozess zum Ausführen der Put-Funktion ist ebenfalls sehr klar: Geben Sie PUT und END aus und schreiben Sie „Hallo, Funktion Put Write“ in die Datei error1.
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()

那么按理说, 输出应该如同上面说的那样, 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

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

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


Das obige ist der detaillierte Inhalt vonDinge, die beim Teilen von Dateiobjekten zwischen übergeordneten und untergeordneten Prozessen bei der Mutilprocessing-Verarbeitung in Python zu beachten sind. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn