Python — это язык, который выполняется в интерпретаторе. Согласно исследованиям, в Python есть глобальная блокировка (GIL). При использовании многопоточности (Thread) нельзя задействовать многоядерность. Однако использование многопроцессности (Multiprocess) позволяет задействовать преимущества многоядерности и действительно повысить эффективность.

Сравнительный эксперимент

Согласно информации, если многопоточный процесс являетсятребовательным к ЦП, то многопоточность не может значительно повысить эффективность; наоборот, она может даже снизить эффективность из-за частого переключения потоков, поэтому рекомендуется многопроцессность; если онинтенсивен по вводу-выводу, то многопоточные процессы могут использовать время простоя во время ожидания блокировки ввода-вывода для выполнения других потоков, повышая эффективность. Поэтому мы сравниваем эффективность различных сценариев с помощью экспериментов.

Операционная система Процессор Память Жёсткий диск
Windows 10 Двухъядерный 8GB Механический жёсткий диск

(1) Импортируйте необходимые модули

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

(2) Определите функцию вычислений, интенсивно использующую CPU

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

(3) Определите IO-интенсивную функцию чтения/записи файлов

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) Определите функцию сетевого запроса

_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) Проверьте время, необходимое для линейного выполнения IO-интенсивных операций, CPU-интенсивных операций и операций, интенсивно использующих сетевые запросы

# 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)

Вывод

  • CPU-интенсивные: 95.6059999466, 91.57099986076355, 92.52800011634827, 99.96799993515015

  • IO-интенсивные: 24.25, 21.76699995994568, 21.769999980926514, 22.060999870300293

  • Интенсивные по сетевым запросам: 4.519999980926514, 8.563999891281128, 4.371000051498413, 4.522000074386597, 14.671000003814697

(6) Проверьте время, необходимое для одновременного многопоточного выполнения CPU-интенсивных операций

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)

Вывод: 25.69700002670288、24.02400016784668

(7) Проверьте время, необходимое для одновременного многопоточного выполнения IO-интенсивных операций

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)

Вывод: 99.9240000248 、101.26400017738342、102.32200002670288

(8) Проверка времени, необходимого для параллельного многопоточного выполнения сетевых ресурсоёмких операций

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)

Вывод: 0.7419998645782471、0.3839998245239258、0.3900001049041748

(9) Проверка времени, необходимого для параллельного многопроцессного выполнения операций, интенсивно использующих CPU

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)

Вывод: 54.342000007629395、53.437999963760376

(10) Проверка параллельного многопроцессного выполнения операций с интенсивным вводом-выводом

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)

Вывод: 12.509000062942505、13.059000015258789

(11) Проверка параллельного многопроцессного выполнения операций с интенсивными HTTP-запросами

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)

Вывод: 0.5329999923706055、0.4760000705718994

Результаты эксперимента

Операции, интенсивно использующие CPU Операции с интенсивным вводом-выводом Операции с интенсивными сетевыми запросами
Линейные операции 94.91824996469 22.46199995279 7.3296000004
Многопоточные операции 101.1700000762 24.8605000973 0.5053332647
Многопроцессные операции 53.8899999857 12.7840000391 0.5045000315

Из приведённых выше результатов видно:

  • Многопоточность, похоже, также не имеет значительного преимущества в IO-интенсивных операциях (возможно, если бы задачи ввода-вывода были тяжелее, преимущество проявилось бы). В CPU-интенсивных операциях она явно уступает однопоточному линейному выполнению. Однако для таких операций, как сетевые запросы, которые вызывают блокировку потоков в режиме активного ожидания, многопоточность имеет очень значительное преимущество.

  • Многопроцессность показывает преимущества в производительности в сценариях CPU-интенсивных, IO-интенсивных и интенсивных по сетевым запросам (операциях, где часто возникает блокировка потоков). Однако для операций, похожих на интенсивные по сетевым запросам, она почти такая же, как многопоточность, но потребляет больше ресурсов, таких как CPU. Поэтому в этом случае мы можем выбрать многопоточность для выполнения.

Исходный адрес: 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/