Maison  >  Article  >  développement back-end  >  Présentation des processus et des threads dans le développement Python

Présentation des processus et des threads dans le développement Python

零下一度
零下一度original
2017-06-25 10:20:291531parcourir

Préface

Aperçu des processus et des fils de discussion :

 De nombreux étudiants ont entendu dire que les systèmes d'exploitation modernes, tels que Mac OS X, UNIX, Linux, Windows, etc., prennent tous en charge le « multitâche ».

Qu'est-ce que le « multitâche » ? En termes simples, le système d'exploitation peut exécuter plusieurs tâches en même temps. Par exemple, vous surfez sur Internet à l'aide d'un navigateur, écoutez des lecteurs MP3 et rattrapez vos devoirs dans Word. Il s'agit d'un travail multitâche. Au moins trois tâches sont exécutées en même temps. De nombreuses tâches s'exécutent silencieusement en arrière-plan en même temps, mais elles ne sont pas affichées sur le bureau.

De nos jours, les processeurs multicœurs sont devenus très populaires, mais même les anciens processeurs monocœur peuvent également effectuer plusieurs tâches. Puisque le code d’exécution du processeur est exécuté séquentiellement, comment un processeur monocœur effectue-t-il plusieurs tâches ?

La réponse est que le système d'exploitation se relaye pour laisser chaque tâche s'exécuter alternativement , la tâche 1 s'exécute pendant 0,01 seconde, passe à la tâche 2, la tâche 2 s'exécute pendant 0,01 seconde, puis passe à la tâche 3. Exécutez pendant 0,01 seconde... et exécutez-la à plusieurs reprises. En apparence, chaque tâche est exécutée alternativement, mais comme la vitesse d'exécution du CPU est si rapide, nous avons l'impression comme si toutes les tâches étaient exécutées en même temps.

Le véritable parallélisme l'exécution du multitâche ne peut être implémenté que sur un processeur multicœur. un grand nombre de tâches Bien plus que le nombre de cœurs de processeur, le système d'exploitation planifiera automatiquement de nombreuses tâches sur chaque cœur à tour de rôle.

Pour le système d'exploitation, une tâche est un processus. Par exemple, l'ouverture d'un navigateur démarre un processus de navigateur, et l'ouverture d'un bloc-notes démarre un processus de bloc-notes, l'ouverture de deux blocs-notes démarre deux blocs-notes. processus, et l’ouverture d’un mot démarre un processus Word.

Certains processus peuvent faire plus d'une chose en même temps, comme Word, qui peut effectuer la saisie, la vérification orthographique, l'impression et d'autres choses en même temps. Au sein d'un processus, si vous souhaitez faire plusieurs choses en même temps, vous devez exécuter plusieurs « sous-tâches » en même temps. Nous appelons ces « sous-tâches » au sein du processus. Fil(Fil).

Puisque chaque processus doit faire au moins une chose, un processus a au moins un thread . Bien entendu, un processus complexe comme Word peut avoir plusieurs threads, et plusieurs threads peuvent être exécutés en même temps. La méthode d'exécution du multi-threading est la même que celle du multi-processus, et elle est également contrôlée par le système d'exploitation. sur plusieurs processus. Basculez rapidement entre les threads afin que chaque thread alterne brièvement et semble s'exécuter simultanément. Bien sûr, vraiment exécuter plusieurs threads simultanément nécessite un processeur multicœur pour être possible. Tous les programmes Python que nous avons écrits précédemment sont des processus qui effectuent des tâches uniques, c'est-à-dire qu'il n'y a qu'un seul thread. Et si nous voulons effectuer plusieurs tâches en même temps ?

 

Il existe deux solutions :

 La première consiste à démarrer plusieurs processus, bien que chaque processus n'ait qu'un seul Thread, mais plusieurs processus peuvent effectuer plusieurs tâches ensemble.

Une autre méthode consiste à démarrer un processus et à démarrer plusieurs threads en un seul processus, afin que plusieurs threads puissent effectuer plusieurs tâches ensemble.

Bien sûr, il existe une troisième méthode, qui consiste à démarrer plusieurs processus, et chaque processus démarre plusieurs threads, afin que plus de tâches puissent être exécutées en même temps. le modèle est plus complexe, en fait rarement utilisé

.

Pour résumer, il existe trois façons de réaliser le multitâche :


  • Mode multi-processus ;
    • Mode multi-thread 🎜>
    • Multi-processus + multi-thread ; mode.

Exécuter plusieurs tâches en même temps. Habituellement, les tâches ne sont pas sans rapport, mais doivent communiquer et se coordonner les unes avec les autres. Parfois, la tâche 1 doit faire une pause et attendre la tâche. 2 pour terminer. Pour continuer l'exécution. Parfois, la tâche 3 et la tâche 4 ne peuvent pas être exécutées en même temps. Par conséquent, la complexité des programmes multi-processus et multi-thread est beaucoup plus élevée que celle du programme mono-processus et mono-thread. a écrit plus tôt.

En raison de la grande complexité et de la difficulté du débogage, nous ne voulons pas écrire du multitâche à moins que nous y soyons obligés. Cependant, il arrive souvent qu’il soit impossible de se passer du multitâche. Pensez à regarder un film sur un ordinateur. Un thread doit lire la vidéo et un autre thread lit l'audio. Sinon, si elle est implémentée dans un seul thread, la vidéo doit être lue en premier, puis l'audio, ou l'audio doit être lu en premier et. alors la vidéo. Ce n'est évidemment pas possible.

Python prend en charge à la fois le multitraitement et le multithreading. Nous verrons comment écrire ces deux types de programmes multitâches.

Processus

Première connaissance :

 Afin d'implémenter le multi-traitement dans un programme Python, nous devons d'abord comprendre les connaissances pertinentes du système d'exploitation. Le système d'exploitation Unix/Linux fournit un appel système fork(), ce qui est très spécial. Les appels de fonction ordinaires sont appelés une fois et renvoient une fois, mais fork() est appelé une fois et renvoie deux fois car le système d'exploitation copie automatiquement le processus actuel (appelé processus parent) des copies (appelées processus enfants), puis reviennent respectivement dans le processus parent et le processus enfant. Le processus enfant renvoie toujours 0, tandis que le processus parent renvoie l'ID du processus enfant. La raison en est qu'un processus parent peut débourser de nombreux processus enfants, le processus parent doit donc enregistrer l'ID de chaque processus enfant, et le processus enfant n'a besoin que d'appeler getppid() Vous pouvez obtenir l'ID du processus parent. Le module os de Python encapsule les appels système courants, y compris fork, qui peuvent être facilement utilisés dans les programmes Python Créer un processus enfant :

import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
    print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
    print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
    
# Process (44587) start...
# I (44587) just created a child process (44588).
# I am child process (44588) and my parent is 44587.

Étant donné que Windows n'a pas d'appel fork, le code ci-dessus ne s'exécutera pas sous Windows. Le système Mac étant basé sur le noyau BSD (un type d'Unix), il n'y a aucun problème à l'exécuter sur un Mac. Il est recommandé d'utiliser un Mac pour apprendre Python ! Avec l'appel fork, lorsqu'un processus reçoit une nouvelle tâche, il peut copier un processus enfant pour gérer la nouvelle tâche. Un serveur Apache commun fait écouter le processus parent sur le port. Chaque fois qu'il y a une nouvelle requête http, il se lance. out. Processus enfant pour gérer les nouvelles requêtes http.

module multitraitement :

Si vous envisagez d'écrire un programme de service multi-processus, Unix/ Linux est sans aucun doute le meilleur choix, c'est le bon choix. Puisque Windows n'a pas d'appels fork, est-il impossible d'écrire des programmes multi-processus en Python sous Windows ? Étant donné que Python est multiplateforme, il devrait naturellement fournir une prise en charge multi-processus multiplateforme. Le module multiprocessing est une version multiplateforme d'un module multi-processus. Le module multiprocessing fournit une classe Process pour représenter un objet de processus. L'exemple suivant montre le démarrage d'un processus enfant et l'attente de sa fin :

.
import os
import time

# 子进程要执行的代码
def run_proc(name):
    time.sleep(1)
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))    # args里面为何要用,隔开?
    p.start()                                        # 子进程启动,不加这个子进程不执行
    p.join()             # 等待子进程p的执行完毕后再向下执行,不加此项,主程序执行完毕,子进程依然会继续执行不受影响
    print('Child process end.'),

# Parent process 8428.
# Run child process test (9392)...
# Child process end.
<span style="font-size: 13px; color: #800000">Process实例化时执行self._args = tuple(args)操作,如果不用,隔开生成的slef._args就是一个个字母了,传入两个参数以上是就不用加,号了,如下:<br></span>
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},                 *, daemon=None):assert group is None, 'group argument must be None for now'count = next(_process_counter)
        self._identity = _current_process._identity + (count,)
        self._config = _current_process._config.copy()
        self._parent_pid = os.getpid()
        self._popen = None
        self._target = target
        self._args = tuple(args)

a =('ers')
b = tuple(a)print(b)# ('e', 'r', 's')a1 =('ers','gte')
b1 = tuple(a1)print(b1)# ('ers', 'gte')
Code de processus

Pool de processus :

Si vous souhaitez démarrer un grand nombre de processus enfants, vous pouvez utiliser le pool de processus pour créer des processus enfants par lots :

from multiprocessing import Pool,cpu_count
import os, time, random

def long_time_task(name):
    print(&#39;Run task %s (%s)...&#39; % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print(&#39;Task %s runs %0.2f seconds.&#39; % (name, (end - start)))

def Bar(arg):
    print(&#39;-->exec done:&#39;,arg,os.getpid())

if __name__==&#39;__main__&#39;:
    print(&#39;Parent process %s.&#39; % os.getpid())
    p = Pool(cpu_count())               # 获取当前cpu核数,多核cpu的情况下多进程才能实现真正的并发
    for i in range(5):
        # p.apply_async(func=long_time_task, args=(i,), callback=Bar) #callback回调 执行完func后再执行callback 用主程序执行
        p.apply_async(long_time_task, args=(i,))
    print(&#39;Waiting for all subprocesses done...&#39;)
    p.close()
    p.join()              # !等待进程池执行完毕,不然主进程执行完毕后,进程池直接关闭
    print(&#39;All subprocesses done.&#39;)

# Parent process 4492.
# Waiting for all subprocesses done...
# Run task 0 (3108)...
# Run task 1 (7936)...
# Run task 2 (11236)...
# Run task 3 (8284)...
# Task 2 runs 0.86 seconds.
# Run task 4 (11236)...
# Task 0 runs 1.34 seconds.
# Task 1 runs 1.49 seconds.
# Task 3 runs 2.62 seconds.
# Task 4 runs 1.90 seconds.
# All subprocesses done.

重点:另进程池里的进程执行完毕后,进程关闭自动销毁,不再占用内存,同理,非进程池创建的子进程,执行完毕后也是自动销毁,具体测试如下:

from multiprocessing import Pool,cpu_countimport os, time, randomdef long_time_task(name):print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()print('Task %s runs %0.2f seconds.' % (name, (end - start)))def count_process():import psutil
    pids = psutil.pids()
    process_name = []for pid in pids:
        p = psutil.Process(pid)
        process_name.append(p.name())       # 获取进程名# process_name.append(p.num_threads())       # 获取进程的线程数# print process_nameprint len(process_name)if __name__=='__main__':print('Parent process %s.' % os.getpid())
    p = Pool(4)for i in range(5):
        p.apply_async(long_time_task, args=(i,))print('Waiting for all subprocesses done...')
    count_process()        # 进程池开始时进程数(包含系统其他应用进程)    p.close()
    p.join()             
    count_process()        # 进程池关闭时进程数print('All subprocesses done.')# Parent process 8860.# Waiting for all subprocesses done...# Run task 0 (2156)...# Run task 1 (1992)...# Run task 2 (10680)...# Run task 3 (11216)...# 109           开始# Task 2 runs 0.93 seconds.# Run task 4 (10680)...# Task 1 runs 1.71 seconds.# Task 3 runs 2.01 seconds.# Task 0 runs 2.31 seconds.# Task 4 runs 2.79 seconds.# 105           结束# All subprocesses done.
进程池子进程执行完毕后销毁

代码解读:

Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

请注意输出的结果,task 0123是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:

p = Pool(5)

就可以同时跑5个进程。

由于Pool的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。

 

进程间通信:

  Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。

我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
    print(&#39;Process to write: %s&#39; % os.getpid())
    for value in [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;]:
        print(&#39;Put %s to queue...&#39; % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    print(&#39;Process to read: %s&#39; % os.getpid())
    while True:
        value = q.get(True)
        print(&#39;Get %s from queue.&#39; % value)

if __name__==&#39;__main__&#39;:
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 启动子进程pr,读取:
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()      # 强制关闭子进程

# Process to write: 9472
# Put A to queue...
# Process to read: 3948
# Get A from queue.
# Put B to queue...
# Get B from queue.
# Put C to queue...
# Get C from queue.

在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所有,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了。

 

进程间共享数据:

有时候我们不仅仅需要进程间数据传输,也需要多进程间进行数据共享,即可以使用同一全局变量;如:为何下面程序的列表输出为空?

from multiprocessing import Process, Manager
import os

# manager = Manager()
vip_list = []
#vip_list = manager.list()

def testFunc(cc):
    vip_list.append(cc)
    print &#39;process id:&#39;, os.getpid()

if __name__ == &#39;__main__&#39;:
    threads = []

    for ll in range(10):
        t = Process(target=testFunc, args=(ll,))
        t.daemon = True
        threads.append(t)

    for i in range(len(threads)):
        threads[i].start()

    for j in range(len(threads)):
        threads[j].join()

    print "------------------------"
    print &#39;process id:&#39;, os.getpid()
    print vip_list

# process id: 9436
# process id: 11120
# process id: 10636
# process id: 1380
# process id: 10976
# process id: 10708
# process id: 2524
# process id: 9392
# process id: 10060
# process id: 8516
# ------------------------
# process id: 9836
# []

如果你了解 python 的多线程模型,GIL 问题,然后了解多线程、多进程原理,上述问题不难回答,不过如果你不知道也没关系,跑一下上面的代码你就知道是什么问题了。因为进程间内存是独立的

正如上面提到的,在进行并发编程时,最好尽可能避免使用共享状态。在使用多个进程时尤其如此。但是,如果您确实需要使用一些共享数据,那么多处理提供了两种方法。

共享内存:

数据可以使用值或数组存储在共享内存映射中。例如,下面的代码:

from multiprocessing import Process, Value, Array

def f(n, a):
    n.value = 3.1415927
    for i in range(len(a)):
        a[i] = -a[i]

if __name__ == &#39;__main__&#39;:
    num = Value(&#39;d&#39;, 0.0)
    arr = Array(&#39;i&#39;, range(10))

    p = Process(target=f, args=(num, arr))
    p.start()
    p.join()

    print num.value
    print arr[:]
    
# 3.1415927
# [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

在创建num和arr时使用的“i”和“i”参数是数组模块使用的类型的类型:“表示双精度浮点数”,“i”表示一个已签名的整数。这些共享对象将是进程和线程安全的。为了更灵活地使用共享内存,您可以使用多处理。sharedctypes模块支持创建从共享内存中分配的任意类型的ctypes对象。

服务进程:

manager()返回的manager对象控制一个保存Python对象的服务器进程,并允许其他进程使用代理来操作它们。manager()返回的管理器将支持类型列<span class="pre">list</span><span class="pre">dict</span><span class="pre">Namespace</span><span class="pre">Lock</span><span class="pre">RLock</span><span class="pre">Semaphore</span><span class="pre">BoundedSemaphore</span><span class="pre">Condition</span><span class="pre">Event</span><span class="pre">Queue</span><span class="pre">Value</span> and <span class="pre">Array</span>。如下:

from multiprocessing import Process, Manager

def f(d, l):
    d[1] = &#39;1&#39;
    d[&#39;2&#39;] = 2
    d[0.25] = None
    l.reverse()

if __name__ == &#39;__main__&#39;:
    manager = Manager()

    d = manager.dict()
    l = manager.list(range(10))

    p = Process(target=f, args=(d, l))
    p.start()
    p.join()

    print d
    print l

# {0.25: None, 1: &#39;1&#39;, &#39;2&#39;: 2}
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

服务器进程管理器比使用共享内存对象更灵活,因为它们可以用来支持任意对象类型。另外,单个管理器可以通过网络上不同计算机上的进程共享。但是,它们比使用共享内存要慢。

更多-》》点击

 

 

小结

在Unix/Linux下,可以使用fork()调用实现多进程。

要实现跨平台的多进程,可以使用multiprocessing模块。

进程间通信是通过Queue(多进程间)Pipes(两个进程间)等实现的。

 

补充小知识点-》父进程开辟子进程,子进程开辟子子进程,如果把子进程杀掉,子子进程会被杀死吗?

import timefrom multiprocessing import Processimport osdef count_process():import psutil
    pids = psutil.pids()print len(pids)def test3():
    count_process()for i in range(10):print "test3 %s"%os.getpid()
        time.sleep(0.5)def test1():print "test1 %s"%os.getpid()
    p2 = Process(target=test3, name="protest2")
    p2.start()
    p2.join()if __name__ == '__main__':
    count_process()
    p1 = Process(target=test1, name="protest1")
    p1.start()
    time.sleep(2)
    p1.terminate()
    time.sleep(2)
    count_process()for i in range(10):print(i)
        time.sleep(1)# 86# test1 9500# 88# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# test3 3964# 87# 0# test3 3964# test3 3964# 1# 2# 3# 4# 5# 6# 7# 8# 9
子子进程的心路历程

 

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