Gestion des sous-processus Python et acquisition d'informations sur les processus

1. Sous-processus du module de sous-processus Python

subprocessLes modules nous permettent de démarrer un nouveau processus et de nous connecter à leurs canaux d'entrée/sortie/erreur pour obtenir des valeurs de retour.

(1) runMéthode

Voyons d'abord runl'utilisation de la méthode.Les paramètres de cette méthode sont les suivants :

  • args: Indique la commande à exécuter. Doit être une chaîne ou une liste d'arguments de chaîne.
  • stdin, stdoutet stderr: l'entrée, la sortie et l'erreur standard du processus enfant. Sa valeur peut être subprocess.PIPE, subprocess.DEVNULL, un descripteur de fichier existant, un objet fichier déjà ouvert ou None. subprocess.PIPEIndique la création d'un nouveau canal pour le processus enfant. subprocess.DEVNULLIndique l'utilisation os.devnull. La valeur par défaut est utilisée None, ce qui signifie ne rien faire. De plus, stderrils peuvent être fusionnés stdoutet édités ensemble.
  • timeout:Définir le délai d'expiration de la commande. Si le délai d'exécution de la commande expire, le processus enfant sera tué et TimeoutExpiredune exception sera levée.
  • check: Si ce paramètre est défini sur Trueet que le code d'état de sortie du processus n'est pas 0, CalledProcessErrorune exception est levée.
  • encoding: Si ce paramètre est spécifié, stdin, stdoutet stderrpeut recevoir des données de chaîne et les encoder dans cet encodage. Sinon, seules bytesles données de type sont reçues.
  • shell: Si ce paramètre est True, la commande spécifiée sera exécutée via le Shell du système d'exploitation.
  • capture_output: Si , et capture_output = Truesera capturé , l' objet interne utilisera automatiquement et pour créer les objets de sortie standard et d'erreur standard lorsqu'il est appelé ; les paramètres et ne peuvent pas être transmis en même temps . Si vous souhaitez capturer et fusionner deux en un, utilisez et .stdoutstderrPopenstdout = PIPEstderr = PIPEstdoutstderrcapture_outputstreamstdout = PIPEstderr = STDOUT

Regardons un exemple ci-dessous. runLa méthode d'appel de méthode renvoie CompletedProcessune instance :

import subprocess

args1 = ['python', 'src/Python子进程测试程序1.py']

ret = subprocess.run(args=args1, capture_output=True, encoding='utf-8')  # 相当于在命令行执行:python src/Python子进程测试程序1.py
print(ret)  # CompletedProcess(args=['python', 'src/Python子进程测试程序1.py'], returncode=0, stdout='子进程输出: Hello World!\n', stderr='')

if ret.returncode == 0:
    print('Success, stdout:', ret.stdout)  # Success, stdout: 子进程输出: Hello World!
else:
    print('Error, stderr:', ret.stderr)  # Error, stderr: python: can't open file 'D:\xxx\src\Python子进程测试程序1_Wrong.py': [Errno 2] No such file or directory

Parmi eux, Python子进程测试程序1.pyle contenu est le suivant :

print('子进程输出: Hello World!')

(2) PopenMéthode

PopenIl constitue subprocessle cœur et gère la création et la gestion des processus enfants.

PopenLes paramètres de la méthode sont les suivants :

  • args: commande Shell, qui peut être un type de chaîne ou de séquence (comme une liste, un tuple).
  • bufsize:Taille du tampon. Utilisé lors de la création d'un objet canal pour un flux standard, la valeur par défaut est -1. 0Indique que le tampon n'est pas utilisé, 1indiquant que la mise en mémoire tampon de ligne n'est disponible universal_newlines = Trueque lorsque , qui est en mode texte. Un nombre positif indique la taille du tampon et un nombre négatif indique l'utilisation de la taille du tampon par défaut du système.
  • stdin, stdout,stderr : représentent respectivement les poignées d'entrée, de sortie et d'erreur standard du programme.
  • preexec_fn: Uniquement valable sous la plateforme Unix, utilisé pour spécifier un objet appelable, qui sera appelé avant l'exécution du processus enfant.
  • shell: Si ce paramètre est True, la commande spécifiée sera exécutée via le Shell du système d'exploitation.
  • cwd: Utilisé pour définir le répertoire actuel du processus enfant.
  • env: Variables d'environnement utilisées pour spécifier le processus enfant. Si tel est le cas env = None, les variables d'environnement du processus enfant seront héritées du processus parent.

Cette méthode créera un Popenobjet Popendoté des méthodes suivantes :

  • poll(): Vérifiez si le processus est terminé, retournez s'il est terminé returncode, sinon retournez None.
  • wait(timeout): Attendez que le processus enfant se termine.
  • communicate(input=None, timeout=None): Interagissez avec le processus enfant, envoyez et lisez des données au processus enfant. Envoie inputles données spécifiées à stdin; lit les données depuis stdoutet jusqu'à la fin du fichier, en attendant la fin du processus. stderrAinsi, la valeur de retour est un tuple : (stdout_data, stderr_data). Si timeoutle processus enfant ne se termine pas dans le délai imparti, TimeoutExpiredune exception sera levée. Il convient de noter qu'après avoir détecté l'exception, vous pouvez appeler à nouveau cette fonction car le processus enfant n'a pas été KILL. Par conséquent, si le programme se termine avec un délai d'attente, le processus enfant doit être TUÉ correctement.
  • send_signal(singnal):Envoyer un signal au processus enfant.
  • terminate(): Arrêtez le processus enfant, c'est-à-dire envoyez SIGTERMun signal au processus enfant.
  • kill(): Tuez le processus enfant et envoyez SIGKILLun signal au processus enfant.

PopenUn exemple de la méthode est le suivant :

args2 = ['python', 'src/Python子进程测试程序2.py']

proc = subprocess.Popen(args=args2,
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        encoding='utf-8')
print(proc)  # <Popen: returncode: None args: ['python', 'src/Python子进程测试程序2.py']>

stdout, stderr = proc.communicate(input='AsanoSaki')
print('stdout:', stdout)  # stdout: 子进程输出:  AsanoSaki
print('stderr:', stderr)  # stderr: 空

Parmi eux, Python子进程测试程序2.pyle contenu est le suivant :

s = input()
print('子进程输出: ', s)

Examinons maintenant communicatel'utilisation et la modification du programme de test pour qu'il s'exécute pendant plus d'une seconde :

args2 = ['python', 'src/Python子进程测试程序2.py']

proc = subprocess.Popen(args=args2,
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        encoding='utf-8')

try:
    stdout, stderr = proc.communicate(input='AsanoSaki', timeout=1)  # 设置1s超时时间
except subprocess.TimeoutExpired:
    print('子进程运行超时')
    proc.kill()  # 需要KILL子进程
    stdout, stderr = proc.communicate()  # 捕获异常之后,可以再次调用该函数
    print('stdout:', stdout)  # stdout: 子进程输出:  AsanoSaki
    print('stderr:', stderr)  # stderr: 空

Le contenu actuel Python子进程测试程序2.pyest le suivant :

s = input()
print('子进程输出: ', s)

for i in range(10**9):
    pass

2. Pool de threads ThreadPoolExecutor

concurrent.futuresLe module est un nouveau module introduit dans Python3.2 pour prendre en charge l'exécution asynchrone et la programmation simultanée efficace dans les processeurs multicœurs et les E/S réseau. La classe de base du pool de threads se trouve concurrent.futuresdans le module Executor, Executorqui fournit deux sous-classes, à savoir ThreadPoolExecutoret ProcessPoolExecutor, pour simplifier la mise en œuvre de la programmation asynchrone multiplateforme. ThreadPoolExecutorest utilisé pour créer un pool de threads et ProcessPoolExecutorest utilisé pour créer un pool de processus. Si vous utilisez un pool de threads/pool de processus pour gérer la programmation simultanée, il vous suffit de soumettre la fonction Task correspondante au pool de threads/pool de processus, et le pool de threads/pool de processus s'occupera du reste.

Tout d'abord, comprenons d'abord les deux méthodes de programmation concurrentes multi-processus et multi-thread :

  • Multi-processus : lorsque la programmation simultanée est implémentée via multi-processus, le programme attribue des tâches à plusieurs processus, et ces processus peuvent s'exécuter simultanément sur différents processeurs . Les processus sont indépendants et chacun possède son propre espace mémoire, etc., permettant une véritable exécution parallèle . Cependant, la communication entre les processus prend du temps et nécessite l'utilisation du mécanisme IPC (Inter-Process Communication), et la commutation entre les processus prend plus de temps que la commutation entre les threads, de sorte que le coût de création d'un processus est plus élevé.
  • Multithreading : lorsque la programmation simultanée est implémentée via le multithreading, le programme attribue des tâches à plusieurs threads, et ces threads peuvent s'exécuter simultanément sur différents cœurs de processeur dans le même processus . L'espace mémoire du processus est partagé entre les threads , la surcharge est donc relativement faible. Cependant, il convient de noter que dans l'interpréteur Python, les threads ne peuvent pas réaliser une véritable exécution parallèle, car Python dispose d'un GIL (verrouillage global de l'interpréteur), qui garantit qu'un seul thread exécute le code Python en même temps. Par conséquent, plusieurs threads dans un processus Python ne peuvent pas s'exécuter en parallèle et les processeurs multicœurs ne peuvent pas être pleinement utilisés lors de l'utilisation d'une programmation multithread.

ThreadPoolExecutorCréez un pool de threads dans lequel les tâches peuvent être soumises pour exécution. ThreadPoolExecutorPlus facile à utiliser que ProcessPoolExecutor, et n'a pas de surcharge comme les processus. Il nous permet d'effectuer une programmation asynchrone cross-thread dans un interpréteur Python car il contourne le GIL.

ExectuorLes méthodes courantes suivantes sont fournies :

  • submit(fn, *args, **kwargs): fnSoumettez la fonction au pool de threads. *argsReprésente fnles paramètres transmis à la fonction, **kwargsqui représente les paramètres transmis à la fonction sous la forme de paramètres de mot-clé fn.
  • map(func, *iterables, timeout=None, chunksize=1): Cette fonction est similaire à la fonction globale map(func, *iterables), sauf qu'elle démarrera plusieurs threads pour exécuter le traitement immédiatement de manière asynchrone .iterablesmap
  • shutdown(wait=True): fermez le pool de threads.

Une fois que le programme a donné fnla fonction submitau pool de threads, submitla méthode renverra un Futureobjet. FutureLa classe est principalement utilisée pour obtenir la valeur de retour de la fonction de tâche de thread. Puisque la tâche du thread sera exécutée de manière asynchrone dans le nouveau thread, la fonction exécutée par le thread est équivalente à une tâche « à terminer dans le futur », elle est donc Pythonreprésentée Futurepar .

FutureL'objet fournit les méthodes suivantes :

  • cancel(): Annulez Futurela tâche de thread représentée par ceci . Si la tâche est en cours d'exécution et ne peut pas être annulée, la méthode renvoie False; sinon, le programme annule la tâche et renvoie True.
  • cancelled(): indique Futuresi la tâche de thread représentée a été annulée avec succès.
  • running(): Si la Futuretâche de thread représentée par est en cours d'exécution et ne peut pas être annulée, cette méthode renvoie True.
  • done(): Si la Funturetâche de thread représentée par ceci est annulée avec succès ou si l'exécution est terminée, cette méthode renvoie True.
  • result(timeout=None): Obtenez Futurele dernier résultat renvoyé par la tâche de thread représentée par this . Si Futurela tâche de thread représentée par n'est pas terminée, cette méthode bloquera le thread actuel, où timeoutle paramètre spécifie le nombre maximum de secondes à bloquer.
  • exception(timeout=None): Obtenez Futurel'exception provoquée par la tâche de thread représentée par this . Si la tâche s'est terminée avec succès sans exception, la méthode renvoie None.
  • add_done_callback(fn): FutureEnregistrez une "fonction de rappel" pour la tâche de thread représentée par . Lorsque la tâche est terminée avec succès, le programme déclenchera automatiquement la fnfonction.

Après avoir utilisé un pool de threads, la méthode du pool de threads shutdown()doit être appelée, ce qui lancera la séquence d'arrêt du pool de threads. Le pool de threads après avoir appelé shutdown()la méthode ne recevra plus de nouvelles tâches, mais terminera toutes les tâches précédemment soumises. Lorsque toutes les tâches du pool de threads ont été exécutées, tous les threads du pool de threads mourront.

Les étapes pour utiliser un pool de threads pour effectuer des tâches de thread sont les suivantes :

  1. Appelez ThreadPoolExecutorle constructeur de la classe pour créer un pool de threads.
  2. Définissez une fonction normale en tant que tâche de thread.
  3. Appelez la méthode ThreadPoolExecutorde l'objet submit()pour soumettre la tâche de thread.
  4. Lorsque vous ne souhaitez soumettre aucune tâche, appelez ThreadPoolExecutorla shutdown()méthode de l'objet pour fermer le pool de threads.

Regardons un exemple ci-dessous :

from concurrent.futures import ThreadPoolExecutor

def thread(num):
    print('Threads:', num)

def getResult():  # 有返回结果的函数
    return 'Get Result'

# 新建ThreadPoolExecutor对象并指定最大的线程数量
with ThreadPoolExecutor(max_workers=3) as executor:
    # 提交多个任务到线程池中
    executor.submit(thread, 1)  # Threads: 1
    executor.submit(thread, 2)  # Threads: 2
    t = executor.submit(getResult)
    print(t.result())  # Get Result

# 或者按如下方式实现
threadPool = ThreadPoolExecutor(max_workers=3)
for i in range(3):
    threadPool.submit(thread, i)
threadPool.shutdown(wait=True)
# Threads: 0
# Threads: 1
# Threads: 2

3. Module d'acquisition d'informations système Psutil

Maintenant, certaines personnes peuvent se demander comment obtenir des informations telles que le temps passé ou l'utilisation de la mémoire du processus/thread enfant lorsqu'il est en cours d'exécution ? Python dispose d'un module tiers psutilspécifiquement utilisé pour obtenir des informations relatives au système d'exploitation et au matériel, telles que : CPU, disque, réseau, mémoire, etc.

Nous devons d’abord l’installer psutil, installez-le simplement directement via pipla commande :

pip install psutil

(1) Afficher les informations relatives au processeur :

import psutil

print(psutil.cpu_count())  # CPU的逻辑数量:12

print(psutil.cpu_count(logical=False))  # CPU的物理核心数量:6

print(psutil.cpu_times())  # CPU的用户/系统/空闲时间
# scputimes(user=26860.4375, system=10963.515624999884, idle=676060.796875, interrupt=740.875, dpc=477.75)

for _ in range(3):
    # interval表示每隔0.5s刷新一次,percpu表示查看所有的CPU使用率
    print(psutil.cpu_percent(interval=0.5, percpu=True))
# [21.2, 0.0, 32.4, 0.0, 16.1, 0.0, 0.0, 0.0, 3.2, 0.0, 0.0, 0.0]
# [25.8, 0.0, 3.1, 0.0, 0.0, 0.0, 3.1, 3.1, 0.0, 0.0, 0.0, 18.8]
# [32.4, 0.0, 21.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 6.2, 0.0]

print(psutil.cpu_stats())  # CPU的统计信息,包括上下文切换、中断、软中断以及系统调用次数等
# scpustats(ctx_switches=3399680458, interrupts=1365489476, soft_interrupts=0, syscalls=2283205750)

print(psutil.cpu_freq())  # CPU的频率
# scpufreq(current=2592.0, min=0.0, max=2592.0)

(2) Afficher les informations relatives à la mémoire et au disque :

print(psutil.virtual_memory())  # 内存使用情况,分别为总内存、可用内存、内存占用率、已使用的内存大小、剩余的内存大小
# svmem(total=17022177280, available=7125008384, percent=58.1, used=9897168896, free=7125008384)

print(psutil.swap_memory())  # 交换内存信息(专门用来临时存储数据)
# sswap(total=6030352384, used=5409898496, free=620453888, percent=89.7, sin=0, sout=0)

print(psutil.disk_partitions())  # 磁盘分区、磁盘使用率和磁盘IO信息
# [sdiskpart(device='C:\\', mountpoint='C:\\', fstype='NTFS', opts='rw,fixed', maxfile=255, maxpath=260),
#  sdiskpart(device='D:\\', mountpoint='D:\\', fstype='NTFS', opts='rw,fixed', maxfile=255, maxpath=260),
#  sdiskpart(device='E:\\', mountpoint='E:\\', fstype='NTFS', opts='rw,fixed', maxfile=255, maxpath=260)]

print(psutil.disk_usage("C:\\"))  # 某个磁盘使用情况
# sdiskusage(total=160253673472, used=101791543296, free=58462130176, percent=63.5)

print(psutil.disk_io_counters())  # 磁盘IO统计信息,分别为读次数、写次数、读的字节数、写的字节数、读时间、写时间
# sdiskio(read_count=1833834, write_count=1831471, read_bytes=69098376704, write_bytes=59881958400, read_time=17952, write_time=2323)

(3) Afficher les informations relatives au réseau :

print(psutil.net_io_counters())  # 网卡的网络IO统计信息
# snetio(bytes_sent=629698806, bytes_recv=1756588411, packets_sent=1280472, packets_recv=2023810, errin=0, errout=0, dropin=0, dropout=0)

print(psutil.net_io_counters(pernic=True))  # 列出所有网卡的信息
# {'Ethernet': snetio(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0, errin=0, errout=0, dropin=0, dropout=0),
#  'Local Area Connection* 3': snetio(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0, errin=0, errout=0, dropin=0, dropout=0),
#  'Local Area Connection* 4': snetio(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0, errin=0, errout=0, dropin=0, dropout=0),
#  ......]

print(psutil.net_if_addrs())  # 网络接口信息
# {'Ethernet': [snicaddr(family=<AddressFamily.AF_LINK: -1>, address='04-D4-C4-74-A4-F0', netmask=None, broadcast=None, ptp=None), snicaddr(family=<AddressFamily.AF_INET: 2>, address='169.254.216.112', netmask='255.255.0.0', broadcast=None, ptp=None)],
#  'Local Area Connection* 3': [snicaddr(family=<AddressFamily.AF_LINK: -1>, address='38-00-25-26-9C-70', netmask=None, broadcast=None, ptp=None), snicaddr(family=<AddressFamily.AF_INET: 2>, address='169.254.169.242', netmask='255.255.0.0', broadcast=None, ptp=None), snicaddr(family=<AddressFamily.AF_INET6: 23>, address='fe80::5e4e:63b6:7416:787b', netmask=None, broadcast=None, ptp=None)],
#  ......]

print(psutil.net_if_stats())  # 网卡的详细信息,包括是否启动、通信类型、传输速度、mtu
# {'Ethernet': snicstats(isup=False, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=0, mtu=1500),
#  'vEthernet (Default Switch)': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=4294, mtu=1500),
#  'Loopback Pseudo-Interface 1': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=1073, mtu=1500),
#  ......]

print(psutil.net_connections())  # 当前机器的网络连接,里面接受一个参数,默认是"inet",当然我们也可以指定为其它,比如"tcp"
# [sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.1', port=1309), raddr=(), status='NONE', pid=7516),
#  sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=9100), raddr=(), status='LISTEN', pid=6004),
#  sconn(fd=-1, family=<AddressFamily.AF_INET6: 23>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=49667), raddr=(), status='LISTEN', pid=3768),
#  ......]

print(psutil.users())  # 当前登录的用户信息
# [suser(name='AsanoSaki', terminal=None, host=None, started=1694966965.3425539, pid=None)]

import datetime
print(psutil.boot_time())  # 系统的启动时间:1694912508.6818905
print(datetime.datetime.fromtimestamp(psutil.boot_time()))  # 2023-09-17 09:01:48.681890

(4) Afficher les informations relatives au processus :

print(psutil.pids())  # 当前存在的所有进程的PID
# [0, 4, 8, 140, 212, 584, 756, 1052, 1160, 1188, 1292, 1364, 1384, ...]

print(psutil.pid_exists(0))  # 某个进程是否存在
# True

print(psutil.process_iter())  # 所有进程对象(Process)组成的迭代器
# <generator object process_iter at 0x00000263C4D2AF20>

print(psutil.Process(pid=10712))  # 根据PID获取一个进程对应的Process对象
# psutil.Process(pid=10712, name='pycharm64.exe', status='running', started='08:56:02')

p = psutil.Process(pid=10712)  # 获取该Process对象

print(p.name())  # 进程名称,pycharm64.exe

print(p.exe())  # 进程的exe路径:E:\PyCharm 2020.3.3\bin\pycharm64.exe

print(p.cwd())  # 进程的工作目录:E:\PyCharm 2020.3.3\jbr\bin

print(p.cmdline())  # 进程启动的命令行:['E:\\PyCharm 2020.3.3\\bin\\pycharm64.exe']

print(p.status())  # 进程状态:running

print(p.username())  # 进程用户名:LAPTOP-23NEHV3U\AsanoSaki

print(p.create_time())  # 进程创建时间,返回时间戳:1694998562.3625667

print(p.cpu_times())  # 进程使用的CPU时间
# pcputimes(user=277.09375, system=34.265625, children_user=0.0, children_system=0.0)

print(p.memory_info())  # 进程所使用的内存
# pmem(rss=1507303424, vms=1790066688, num_page_faults=1466884, peak_wset=1536196608,
#      wset=1507303424, peak_paged_pool=881616, paged_pool=876144, peak_nonpaged_pool=268096,
#      nonpaged_pool=154024, pagefile=1790066688, peak_pagefile=1798070272, private=1790066688)

print(p.num_threads())  # 进程内的线程数量,即这个进程开启了多少个线程:68

Nous utilisons maintenant psutille module pour obtenir ThreadPoolExecutorles informations sur la durée d'exécution de la tâche de thread et sur l'utilisation de la mémoire :

args2 = ['python', 'src/Python子进程测试程序2.py']

proc = subprocess.Popen(args=args2,
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        encoding='utf-8')

def getProcessInfo(pid):
    p = psutil.Process(pid)  # 获取pid所代表的子进程
    start_time = time.time()
    memory = 0
    while(True):
        try:
            memory = max(memory, p.memory_info().rss)
        except:
            break
    runtime = (time.time() - start_time) * 1000
    return runtime, memory

threadPool = ThreadPoolExecutor()
task = threadPool.submit(getProcessInfo, proc.pid)
stdout, stderr = proc.communicate(input='AsanoSaki')
runtime, memory = task.result()
print(runtime)  # 510.7400417327881
print(memory)  # 40865792

threadPool.shutdown(wait=True)
proc.kill()

Parmi eux, Python子进程测试程序2.pyle contenu est le suivant :

s = input()
print('子进程输出: ', s)

Je suppose que tu aimes

Origine blog.csdn.net/m0_51755720/article/details/132948466
conseillé
Classement