Python es un lenguaje que se ejecuta en un intérprete. Según la documentación, Python tiene un bloqueo global (GIL). Al usar múltiples subprocesos (Thread), no se puede aprovechar la ventaja de múltiples núcleos. Sin embargo, al usar múltiples procesos (Multiprocess), se puede aprovechar la ventaja de múltiples núcleos y mejorar verdaderamente la eficiencia.

Experimento comparativo

La documentación indica que si el proceso con múltiples subprocesos esintensivo en CPU, entonces el multihilo no puede mejorar mucho la eficiencia; al contrario, puede incluso reducir la eficiencia debido al cambio frecuente de subprocesos. Se recomienda usar multiproceso; si esintensivo en E/S, el proceso multihilo puede aprovechar el tiempo de inactividad durante la espera de bloqueo de E/S para ejecutar otros subprocesos, mejorando la eficiencia. Por lo tanto, comparamos la eficiencia en diferentes escenarios mediante experimentos.

Sistema operativo CPU Memoria Disco duro
Windows 10 Doble núcleo 8GB Disco duro mecánico

(1) Importar los módulos necesarios

import requests
import time
from threading import Thread
from multiprocessing import Process

(2) Definir funciones de cálculo intensivo de CPU

def count(x, y):
    # 使程序完成50万计算
    c = 0
    while c < 500000:
        c += 1
        x += x
        y += y

(3) Definir funciones de lectura/escritura de archivos intensivas en E/S

def write():
    f = open("test.txt", "w")
    for x in range(5000000):
        f.write("testwrite\n")
    f.close()
def read():
    f = open("test.txt", "r")
    lines = f.readlines()
    f.close()

(4) Definir funciones de solicitudes de red

_head = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36'}
url = "http://www.tieba.com"
def http_request():
    try:
        webPage = requests.get(url, headers=_head)
        html = webPage.text
        return {"context": html}
    except Exception as e:
        return {"error": e}

(5) Probar el tiempo requerido para la ejecución lineal de operaciones intensivas en E/S, operaciones intensivas en CPU y operaciones intensivas en solicitudes de red

# CPU密集操作
t = time.time()
for x in range(10):
    count(1, 1)
print("Line cpu", time.time() - t)
# IO密集操作
t = time.time()
for x in range(10):
    write()
    read()
print("Line IO", time.time() - t)
# 网络请求密集型操作
t = time.time()
for x in range(10):
    http_request()
print("Line Http Request", time.time() - t)

Salida

  • Intensivo en CPU: 95.6059999466, 91.57099986076355, 92.52800011634827, 99.96799993515015

  • Intensivo en E/S: 24.25, 21.76699995994568, 21.769999980926514, 22.060999870300293

  • Intensivo en solicitudes de red: 4.519999980926514, 8.563999891281128, 4.371000051498413, 4.522000074386597, 14.671000003814697

(6) Probar el tiempo requerido para la ejecución concurrente de operaciones intensivas en CPU con múltiples subprocesos (multihilo)

counts = []
t = time.time()
for x in range(10):
    thread = Thread(target=count, args=(1,1))
    counts.append(thread)
    thread.start()
e = counts.__len__()
while True:
    for th in counts:
        if not th.is_alive():
            e -= 1
    if e <= 0:
        break
print(time.time() - t)

Output: 25.69700002670288、24.02400016784668

(7) Probar el tiempo requerido para la ejecución concurrente de operaciones intensivas en E/S con múltiples subprocesos (multihilo)

def io():
    write()
    read()

t = time.time()
ios = []
t = time.time()
for x in range(10):
    thread = Thread(target=count, args=(1,1))
    ios.append(thread)
    thread.start()

e = ios.__len__()
while True:
    for th in ios:
        if not th.is_alive():
            e -= 1
    if e <= 0:
        break
print(time.time() - t)

Output: 99.9240000248 、101.26400017738342、102.32200002670288

(8) Probar el tiempo requerido para la ejecución concurrente de operaciones intensivas en red con múltiples subprocesos (multihilo)

t = time.time()
ios = []
t = time.time()
for x in range(10):
    thread = Thread(target=http_request)
    ios.append(thread)
    thread.start()

e = ios.__len__()
while True:
    for th in ios:
        if not th.is_alive():
            e -= 1
    if e <= 0:
        break
print("Thread Http Request", time.time() - t)

Output: 0.7419998645782471、0.3839998245239258、0.3900001049041748

(9) Probar el tiempo requerido para la ejecución concurrente de operaciones intensivas en CPU con múltiples procesos (multiproceso)

counts = []
t = time.time()
for x in range(10):
    process = Process(target=count, args=(1,1))
    counts.append(process)
    process.start()
e = counts.__len__()
while True:
    for th in counts:
        if not th.is_alive():
            e -= 1
    if e <= 0:
        break
print("Multiprocess cpu", time.time() - t)

Output: 54.342000007629395、53.437999963760376

(10) Probar la ejecución concurrente de operaciones intensivas en E/S con múltiples procesos (multiproceso)

t = time.time()
ios = []
t = time.time()
for x in range(10):
    process = Process(target=io)
    ios.append(process)
    process.start()

e = ios.__len__()
while True:
    for th in ios:
        if not th.is_alive():
            e -= 1
    if e <= 0:
        break
print("Multiprocess IO", time.time() - t)

Output: 12.509000062942505、13.059000015258789

(11) Probar la ejecución concurrente de operaciones intensivas en solicitudes HTTP con múltiples procesos (multiproceso)

t = time.time()
httprs = []
t = time.time()
for x in range(10):
    process = Process(target=http_request)
    ios.append(process)
    process.start()

e = httprs.__len__()
while True:
    for th in httprs:
        if not th.is_alive():
            e -= 1
    if e <= 0:
        break
print("Multiprocess Http Request", time.time() - t)

Output: 0.5329999923706055、0.4760000705718994

Resultados experimentales

Operaciones intensivas en CPU Operaciones intensivas en E/S Operaciones intensivas en solicitudes de red
Operación lineal 94.91824996469 22.46199995279 7.3296000004
Operación con múltiples subprocesos (multihilo) 101.1700000762 24.8605000973 0.5053332647
Operación con múltiples procesos (multiproceso) 53.8899999857 12.7840000391 0.5045000315

A través de los resultados anteriores, podemos ver que:

  • El multihilo no parece tener una gran ventaja en operaciones intensivas en E/S (quizás si las tareas de E/S fueran más pesadas, se podría reflejar la ventaja). En operaciones intensivas en CPU, es claramente peor que la ejecución lineal con un solo subproceso. Sin embargo, para operaciones como solicitudes de red que bloquean subprocesos con espera activa, la ventaja del multihilo es muy notable.

  • El multiproceso muestra ventajas de rendimiento tanto en operaciones intensivas en CPU como en operaciones intensivas en E/S y en operaciones intensivas en solicitudes de red (operaciones donde ocurren bloqueos frecuentes de subprocesos). Sin embargo, en operaciones similares a las intensivas en solicitudes de red, el rendimiento es casi el mismo que el multihilo, pero consume más recursos como CPU. Por lo tanto, en este caso, podemos elegir el multihilo para ejecutar.

Dirección original: http://blog.atomicer.cn/2016/09/30/Python%E4%B8%AD%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%A4%9A%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%AF%B9%E6%AF%94/