1. Sous-processus du module de sous-processus Python
subprocess
Les 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) run
Méthode
Voyons d'abord run
l'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
,stdout
etstderr
: l'entrée, la sortie et l'erreur standard du processus enfant. Sa valeur peut êtresubprocess.PIPE
,subprocess.DEVNULL
, un descripteur de fichier existant, un objet fichier déjà ouvert ouNone
.subprocess.PIPE
Indique la création d'un nouveau canal pour le processus enfant.subprocess.DEVNULL
Indique l'utilisationos.devnull
. La valeur par défaut est utiliséeNone
, ce qui signifie ne rien faire. De plus,stderr
ils peuvent être fusionnésstdout
et é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é etTimeoutExpired
une exception sera levée.check
: Si ce paramètre est défini surTrue
et que le code d'état de sortie du processus n'est pas 0,CalledProcessError
une exception est levée.encoding
: Si ce paramètre est spécifié,stdin
,stdout
etstderr
peut recevoir des données de chaîne et les encoder dans cet encodage. Sinon, seulesbytes
les données de type sont reçues.shell
: Si ce paramètre estTrue
, la commande spécifiée sera exécutée via le Shell du système d'exploitation.capture_output
: Si , etcapture_output = True
sera 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 .stdout
stderr
Popen
stdout = PIPE
stderr = PIPE
stdout
stderr
capture_output
stream
stdout = PIPE
stderr = STDOUT
Regardons un exemple ci-dessous. run
La méthode d'appel de méthode renvoie CompletedProcess
une 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.py
le contenu est le suivant :
print('子进程输出: Hello World!')
(2) Popen
Méthode
Popen
Il constitue subprocess
le cœur et gère la création et la gestion des processus enfants.
Popen
Les 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
.0
Indique que le tampon n'est pas utilisé,1
indiquant que la mise en mémoire tampon de ligne n'est disponibleuniversal_newlines = True
que 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 estTrue
, 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 casenv = None
, les variables d'environnement du processus enfant seront héritées du processus parent.
Cette méthode créera un Popen
objet Popen
doté des méthodes suivantes :
poll()
: Vérifiez si le processus est terminé, retournez s'il est terminéreturncode
, sinon retournezNone
.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. Envoieinput
les données spécifiées àstdin
; lit les données depuisstdout
et jusqu'à la fin du fichier, en attendant la fin du processus.stderr
Ainsi, la valeur de retour est un tuple :(stdout_data, stderr_data)
. Sitimeout
le processus enfant ne se termine pas dans le délai imparti,TimeoutExpired
une 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 envoyezSIGTERM
un signal au processus enfant.kill()
: Tuez le processus enfant et envoyezSIGKILL
un signal au processus enfant.
Popen
Un 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.py
le contenu est le suivant :
s = input()
print('子进程输出: ', s)
Examinons maintenant communicate
l'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.py
est le suivant :
s = input()
print('子进程输出: ', s)
for i in range(10**9):
pass
2. Pool de threads ThreadPoolExecutor
concurrent.futures
Le 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.futures
dans le module Executor
, Executor
qui fournit deux sous-classes, à savoir ThreadPoolExecutor
et ProcessPoolExecutor
, pour simplifier la mise en œuvre de la programmation asynchrone multiplateforme. où ThreadPoolExecutor
est utilisé pour créer un pool de threads et ProcessPoolExecutor
est 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.
ThreadPoolExecutor
Créez un pool de threads dans lequel les tâches peuvent être soumises pour exécution. ThreadPoolExecutor
Plus 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.
Exectuor
Les méthodes courantes suivantes sont fournies :
submit(fn, *args, **kwargs)
:fn
Soumettez la fonction au pool de threads.*args
Représentefn
les paramètres transmis à la fonction,**kwargs
qui 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 globalemap(func, *iterables)
, sauf qu'elle démarrera plusieurs threads pour exécuter le traitement immédiatement de manière asynchrone .iterables
map
shutdown(wait=True)
: fermez le pool de threads.
Une fois que le programme a donné fn
la fonction submit
au pool de threads, submit
la méthode renverra un Future
objet. Future
La 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 Python
représentée Future
par .
Future
L'objet fournit les méthodes suivantes :
cancel()
: AnnulezFuture
la 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 renvoieFalse
; sinon, le programme annule la tâche et renvoieTrue
.cancelled()
: indiqueFuture
si la tâche de thread représentée a été annulée avec succès.running()
: Si laFuture
tâche de thread représentée par est en cours d'exécution et ne peut pas être annulée, cette méthode renvoieTrue
.done()
: Si laFunture
tâ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 renvoieTrue
.result(timeout=None)
: ObtenezFuture
le dernier résultat renvoyé par la tâche de thread représentée par this . SiFuture
la tâche de thread représentée par n'est pas terminée, cette méthode bloquera le thread actuel, oùtimeout
le paramètre spécifie le nombre maximum de secondes à bloquer.exception(timeout=None)
: ObtenezFuture
l'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 renvoieNone
.add_done_callback(fn)
:Future
Enregistrez 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 lafn
fonction.
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 :
- Appelez
ThreadPoolExecutor
le constructeur de la classe pour créer un pool de threads. - Définissez une fonction normale en tant que tâche de thread.
- Appelez la méthode
ThreadPoolExecutor
de l'objetsubmit()
pour soumettre la tâche de thread. - Lorsque vous ne souhaitez soumettre aucune tâche, appelez
ThreadPoolExecutor
lashutdown()
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 psutil
spé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 pip
la 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 psutil
le module pour obtenir ThreadPoolExecutor
les 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.py
le contenu est le suivant :
s = input()
print('子进程输出: ', s)