Maison  >  Questions et réponses  >  le corps du texte

Python有了concurrent的话mutiprocessing和threading还有存在的意义吗?

Python3.2中引入的concurrent非常的好用,只用几行代码就可以编写出线程池/进程池,并且计算型任务效率和mutiprocessing.pool提供的poll和ThreadPoll相比不分伯仲,而且在IO型任务由于引入了Future的概念效率要高数倍。

而threading的话还要自己维护相关的队列防止死锁,代码的可读性也会下降,相反concurrent提供的线程池却非常的便捷,不用自己操心死锁以及编写线程池代码,由于异步的概念IO型任务也更有优势。

既然如此,如果不是为了向下兼容2.x,是不是可以完全没有必要继续使用mutiprocessing和threading了?concurrent如此的优秀。

高洛峰高洛峰2718 Il y a quelques jours877

répondre à tous(3)je répondrai

  • 阿神

    阿神2017-04-18 10:13:53

    Concurrent est en effet très utile, fournissant principalement ThreadPoolExecutor et ProcessPoolExecutor. Un multi-thread, un multi-processus. Mais concurrent est essentiellement une encapsulation de threads et de multitraitements. Vous pouvez le découvrir en consultant son code source.
    ThreadPoolExecutor fournit sa propre file d'attente de tâches, il n'est donc pas nécessaire de l'écrire vous-même. Le soi-disant pool de threads compare simplement le nombre actuel de threads avec la taille définie de max_workers. Si la taille est inférieure à max_workers, la tâche est autorisée à créer des threads pour exécuter la tâche. Vous pouvez consulter le code source

    def _adjust_thread_count(self):

    # When the executor gets lost, the weakref callback will wake up
    # the worker threads.
    def weakref_cb(_, q=self._work_queue):
        q.put(None)
    # TODO(bquinlan): Should avoid creating new threads if there are more
    # idle threads than items in the work queue.
    if len(self._threads) < self._max_workers:
        t = threading.Thread(target=_worker,
                             args=(weakref.ref(self, weakref_cb),
                                   self._work_queue))
        t.daemon = True
        t.start()
        self._threads.add(t)
        _threads_queues[t] = self._work_queue
      

    Donc, si vous gérez la file d'attente vous-même, ce n'est pas un problème. Cocurrent gère également une file d'attente en interne, et elle est juste écrite pour vous.
    En ce qui concerne le problème de blocage, la concurrence peut également provoquer des problèmes de blocage. Laissez-moi vous donner un exemple, exécutez-le et voyez

    import time
    from concurrent.futures import ThreadPoolExecutor
    
    def wait_on_b():
        time.sleep(5)
        print(b.result()) # b will never complete because it is waiting on a.
        return 5
    
    def wait_on_a():
        time.sleep(5)
        print(a.result()) # a will never complete because it is waiting on b.
        return 6
    
    
    executor = ThreadPoolExecutor(max_workers=2)
    a = executor.submit(wait_on_b)
    b = executor.submit(wait_on_a)
    
    

    ProcessPoolExecutor utilise également le multitraitement en interne. Il peut exploiter pleinement les caractéristiques du multicœur et se débarrasser des restrictions du GIL. Notez que lors de la définition de ProcessPoolExecutor(max_workers=2), max_workers est légèrement supérieur au nombre de cœurs de processeur et ne peut pas être trop grand. ProcessPoolExecutor maintient en interne une call_queue pour maintenir la file d'attente des tâches, son type est multiprocessing.Queue. Il existe également un thread qui gère la file d'attente. Cela peut être considéré comme une optimisation du cocourant.
    Vous pouvez voir le code source pour plus de détails. self._adjust_process_count() démarre réellement le processus pour exécuter la tâche. Vous pouvez le connaître en cliquant sur _adjust_process_count. self._queue_management_thread est le thread qui gère la file d'attente

    if self._queue_management_thread is None:
                # Start the processes so that their sentinels are known.
                self._adjust_process_count()
                self._queue_management_thread = threading.Thread(
                        target=_queue_management_worker,
                        args=(weakref.ref(self, weakref_cb),
                              self._processes,
                              self._pending_work_items,
                              self._work_ids,
                              self._call_queue,
                              self._result_queue))
                self._queue_management_thread.daemon = True
                self._queue_management_thread.start()
                _threads_queues[self._queue_management_thread] = self._result_queue
    

    Le cocourant est donc facile à utiliser, c'est-à-dire qu'il effectue lui-même un meilleur traitement, comme la maintenance des files d'attente et la gestion des threads de file d'attente, vous n'avez donc plus à vous en soucier. Bien entendu, vous pouvez également le mettre en œuvre vous-même. Vous pouvez y parvenir avec la simultanéité. Cela peut être réalisé avec le threading et le multitraitement, vous devez tout au plus effectuer un travail supplémentaire vous-même. Parce que le courant simultané utilise essentiellement ces deux noyaux. Bien sûr, il serait préférable que vous disposiez d’une meilleure cocourant déjà disponible. Vous pouvez l’utiliser directement au lieu de réinventer la roue vous-même. Donc, celui à utiliser dépend de votre familiarité personnelle. Par exemple, j'utilise python2, mais je ne peux pas utiliser le cocurrent. J'ai dû utiliser le threading.

    répondre
    0
  • 阿神

    阿神2017-04-18 10:13:53

    La personne ci-dessus l'a déjà dit très clairement, je veux juste ajouter un petit peu.
    Concurrent.future utilise le concept d'asynchrone pour gérer les threads/processus, mais il n'encapsule pas réellement les E/S asynchrones, donc le l'interrogateur a dit que l'amélioration de l'efficacité des IO est en fait fausse.

    répondre
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-18 10:13:53

    Concurrent est une coroutine, pas un fil, deux concepts.

    répondre
    0
  • Annulerrépondre