Maison  >  Article  >  développement back-end  >  Points à noter lors du partage d'objets fichier entre les processus parent et enfant dans mutilprocessing Traitement en Python

Points à noter lors du partage d'objets fichier entre les processus parent et enfant dans mutilprocessing Traitement en Python

巴扎黑
巴扎黑original
2017-04-15 09:04:481647parcourir

Module multi-processus python multiprocessing, donc Processing est aussi le chouchou du multi-processus Mais les problématiques abordées aujourd'hui semblent attirer notre attention

Allez directement au 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()

Le but du code ci-dessus est très clair : utiliser multiprocessing.Process pour dériver un processus pour exécuter la fonction put. La fonction put est également très claire. Elle génère PUT et END et écrit "bonjour, func. put write" dans le fichier error1.log. .

Ensuite, il va de soi que la sortie doit être comme mentionné ci-dessus, PUT et END, puis error1.log aura la phrase "bonjour, func put write ". Cependant, les choses sont toujours un peu imprévisibles. , le résultat de l'exécution du code est :

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

quoi ! ? Pourquoi n'y a-t-il rien dans error1.log !?

Ajustons un peu le code et assistons à nouveau à la magie :

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

Résultat de sortie :

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

Y a-t-il un sentiment de confusion !?

Maintenant, deux questions se posent dans mon esprit :

  1. Pourquoi le premier programme ne peut-il pas écrire cette phrase, mais le second ? Mais ça marche ?

  2. C'est quoi ce 4075 ?
    Avant d'expliquer ces questions, nous devons être clairs sur les caractéristiques de la bibliothèque IO standard : Mémoire tampon complète , mise en mémoire tampon de ligne, pas de mise en mémoire tampon

Pour plus de détails, veuillez consulter le billet de blog précédent : https://my.oschina.net/u/2291...

Parce que maintenant, il est écrit dans le fichier, donc les E/S du système adopteront une méthode entièrement tamponnée, c'est-à-dire que le tampon sera rempli avant d'être vidé dans la file d'attente d'écriture du système.

Donc les problèmes ci-dessus sont résolus immédiatement, à cause de ceux-ci. Le mystérieux « o » remplit tout le tampon, donc le système vide notre contenu dans la file d'attente d'écriture, alors d'où vient 4075 ? Il s'agit d'utiliser 4096-sizeof("bonjour, func put writen")+1, pourquoi avons-nous besoin de + 1. Comme le tampon n'est pas plein, il doit être plus grand pour déclencher l'action d'écriture.

Nous pouvons donc maintenant obtenir la réponse. Si nous voulons écrivez des fichiers de la même manière que ci-dessus dans multiprcessing.Process , il existe trois façons d'y parvenir :

  • Tampon complet

  • Appel manuel flush()

  • Définir l'objet fichier pour qu'il ne soit pas mis en mémoire tampon
    Les première et deuxième options ont été expliquées ci-dessus, parlons donc brièvement de la troisième option :

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

L'image ci-dessus illustre, nous permet de définir la mise en mémoire tampon sur 0 lorsqu'elle est ouverte, ce qui signifie qu'il s'agit d'un mode sans tampon. Ensuite, chaque fois que nous écrivons, nous écrivons directement dans la file d'attente d'écriture au lieu d'écrire dans. le tampon. (La méthode la moins performante)

----------------------------------- -------------Je coupe la ligne--------------------------------- ---------------

Après avoir parlé du phénomène et des méthodes de traitement, nous devrions approfondir ;

Je crois que nous l'avons déjà essayé Quand. l'objet fichier n'est pas explicitement fermé ou flush est explicitement appelé, le fichier peut toujours être écrit normalement. Alors que se passe-t-il ?

En fait, lorsque nous fermons le programme normalement, le processus fera quelques "mains-". on" pour nous après la sortie, comme la fermeture des descripteurs de fichiers ouverts et le nettoyage des fichiers temporaires. Nettoyer la mémoire, etc. C'est précisément à cause de cette "bonne habitude" du système que nos données peuvent être vidées dans la file d'attente d'écriture lorsque le le descripteur de fichier est fermé et le contenu du fichier ne sera pas perdu.

Donc, sur la base de cette compréhension, revenons sur le problème tout à l'heure. Lorsque le processus enfant appelle put, en théorie, le descripteur de fichier est. pas fermé à la fin du programme, donc les données sont perdues dans le tampon

Après avoir suivi les indices, regardons l'implémentation de 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)

puis regardons comment Popn fonctionne.

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)

La partie clé est la dernière os._exit(code). Pourquoi est-elle considérée comme la plus critique Parce que la sortie de cette partie déterminera à quelles "mains" le processus s'occupera ? avec,

Qu'est-ce que c'est que os._exit ? C'est en fait _eixt de la bibliothèque standard, donc on peut réapprendre cette chose simplement

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

Dans le lien ci-dessus, nous pouvons clairement voir que _exit() et exit() sont deux choses différentes, _exit() est simple et violent, rejetant directement le contenu du mode utilisateur et entrant le noyau, tandis que exit() nettoie pour nous plus patiemment

Alors pouvons-nous supposer : Si la sortie de Popen n'est pas os._exit() Quel sera l'effet ?

Heureusement, sys. exit() est le exit() dont nous avons besoin en premier. Sans plus tarder, essayons-le rapidement !

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)

Testez le code et renvoyez la version originale sans remplissage 'o'

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

Nous pouvons voir qu'il peut effectivement être écrit, ce qui prouve que l'affirmation ci-dessus est tenable

Mais il vaut mieux ne pas changer le code source au hasard. Après tout, ce sont les résultats de nombreuses années d'optimisation. par des personnes âgées. Peut-être qu'ils les ont écrits délibérément pour éviter certains problèmes. Il est préférable de standardiser votre comportement et d'essayer de réduire ces choses apparemment désagréables.
Bienvenue à tous les experts pour donner des conseils et échanger. indiquer : https://segmentfault.com/a/11...


module multi-processus python multitraitement, donc, Processing est aussi le chouchou du multi -processus. Mais les problèmes abordés aujourd'hui semblent attirer notre attention

Aller directement au 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()

Le but du code ci-dessus est très clair : utiliser le multiprocessing.Process pour dériver un processus pour exécuter la fonction put. La fonction de la fonction put est également très claire, affichez PUT et END, et écrivez "hello, func put write" dans le fichier error1.

.

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

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

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


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn