Основы многопроцессорного программирования Python — графическая версия

Redis задняя часть Python Операционная система
Основы многопроцессорного программирования Python — графическая версия

Знание многопроцессорного программирования является необходимым знанием для продвинутых программистов Python.Мы обычно привыкли использовать многопроцессорную библиотеку для управления многопроцессорностью, но мы не знаем конкретного принципа ее реализации. Ниже я кратко перечисляю общеизвестные моменты многопроцессорности и использую собственный вызов многопроцессорного метода, чтобы помочь читателям понять механизм реализации многопроцессорности. Код работает в среде Linux. Если нет условия Linux, вы можете использовать докер или виртуальную машину, чтобы испытать его.

docker pull python:2.7

порождать дочерний процесс

Python порождает подпроцесс, используяos.fork(), он порождает дочерний процесс. Вызов fork возвращается одновременно в родительском процессе и в основном процессе, возвращает pid дочернего процесса в родительском процессе и возвращает 0 в дочернем процессе.Если возвращаемое значение меньше нуля, это означает, что сбой дочернего процесса, как правило, из-за нехватки ресурсов операционной системы.

import os

def create_child():
    pid = os.fork()
    if pid > 0:
        print 'in father process'
        return True
    elif pid == 0:
        print 'in child process'
        return False
    else:
        raise

порождать несколько дочерних процессов

мы называемcreate_childМетод может генерировать несколько дочерних процессов несколько раз при условии, что это должно быть гарантировано.create_childОн выполняется в родительском процессе, если это дочерний процесс, не вызывайте его.

# coding: utf-8
# child.py
import os

def create_child(i):
    pid = os.fork()
    if pid > 0:
        print 'in father process'
        return pid
    elif pid == 0:
        print 'in child process', i
        return 0
    else:
        raise

for i in range(10):  # 循环10次,创建10个子进程
    pid = create_child(i)
    # pid==0是子进程,应该立即退出循环,否则子进程也会继续生成子进程
    # 子子孙孙,那就生成太多进程了
    if pid == 0:
        break

бегатьpython child.py, вывод

in father process
in father process
in child process 0
in child process 1
in father process
in child process 2
in father process
in father process
in child process 3
in father process
in child process 4
in child process 5
in father process
in father process
in child process 6
in child process 7
in father process
in child process 8
in father process
in child process 9

процесс сна

Используйте time.sleep, чтобы перевести процесс в спящий режим на любое время, единицей измерения являются секунды, которые могут быть десятичными.

import time

for i in range(5):
    print 'hello'
    time.sleep(1)  # 睡1s

убить дочерний процесс

Используйте os.kill(pid, sig_num) для отправки сигнала дочернему процессу с номером процесса pid. sig_num обычно используется SIGKILL (насильственное убийство, эквивалентно kill -9), SIGTERM (уведомление другой стороны о выходе, эквивалентно kill без параметров) ), SIGINT (эквивалентно Ctrl+C на клавиатуре).

# coding: utf-8
# kill.py

import os
import time
import signal


def create_child():
    pid = os.fork()
    if pid > 0:
        return pid
    elif pid == 0:
        return 0
    else:
        raise


pid = create_child()
if pid == 0:
    while True:  # 子进程死循环打印字符串
        print 'in child process'
        time.sleep(1)
else:
    print 'in father process'
    time.sleep(5)  # 父进程休眠5s再杀死子进程
    os.kill(pid, signal.SIGKILL)
    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还有输出

бегатьpython kill.py, мы видим вывод консоли следующим образом

in father process
in child process
# 等1s
in child process
# 等1s
in child process
# 等1s
in child process
# 等1s
in child process
# 等了5s

Это означает, что после выполнения OS.Kill дочерний процесс перестал выводить

зомби дочерний процесс

В приведенном выше примере после выполнения os.kill мы можем быстро наблюдать за состоянием процесса с помощью ps -ef|grep python и обнаруживать, что дочерний процесс имеет странный вид.<defunct>

root        12     1  0 11:22 pts/0    00:00:00 python kill.py
root        13    12  0 11:22 pts/0    00:00:00 [python] <defunct>

После завершения родительского процесса дочерний процесс также исчезает. Тот<defunct>Что это означает? Это означает «зомби-процесс». После того, как дочерний процесс завершится, он сразу же станет зомби-процессом, и ресурсы операционной системы, занятые зомби-процессом, не будут освобождены сразу, это как труп, который ничего не делает, но продолжает занимать ресурсы операционной системы ( память и др.).

Сбор дочернего процесса

Если полностью убить зомби процесс? Родительский процесс должен вызвать функцию waitpid(pid, options), чтобы «собрать» дочерний процесс, чтобы его можно было уничтожить. Функция waitpid возвращает статус завершения дочернего процесса, который похож на последние слова, оставленные дочерним процессом.

# coding: utf-8

import os
import time
import signal


def create_child():
    pid = os.fork()
    if pid > 0:
        return pid
    elif pid == 0:
        return 0
    else:
        raise


pid = create_child()
if pid == 0:
    while True:  # 子进程死循环打印字符串
        print 'in child process'
        time.sleep(1)
else:
    print 'in father process'
    time.sleep(5)  # 父进程休眠5s再杀死子进程
    os.kill(pid, signal.SIGTERM)
    ret = os.waitpid(pid, 0)  # 收割子进程
    print ret  # 看看到底返回了什么
    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在

Запуск python kill.py выводит следующее

in father process
in child process
in child process
in child process
in child process
in child process
in child process
(125, 9)

Мы видим, что waitpid возвращает кортеж, первый — это pid дочернего процесса, а что означает второй 9. Он имеет разное значение в разных операционных системах, но в Unix его обычное значение — это 16-битное целое значение. , первые 8 бит представляют статус выхода процесса, а последние 8 бит представляют собой целочисленное значение сигнала, вызвавшего выход процесса. Таким образом, в этом примере бит состояния выхода равен 0, а номер сигнала равен 9. Помните.kill -9Эта команда, is this 9, означает принудительно убить процесс.

Если мы изменим OS.kill к сигналу, чтобы увидеть результат, такой как OS.Kill (PID, SIGNAL.SIGTERM), мы можем видеть, что возвращенный результат становится(138, 15), 15 — целочисленное значение сигнала SIGTERM.

waitpid(pid, 0)Он также может играть функцию ожидания окончания дочернего процесса, если дочерний процесс не завершится, вызов всегда будет зависать.

захват сигнала

Действие обработки сигнала SIGTERM по умолчанию заключается в выходе из процесса.На самом деле, мы также можем установить функцию обработки сигнала SIGTERM так, чтобы он не завершался.

# coding: utf-8

import os
import time
import signal


def create_child():
    pid = os.fork()
    if pid > 0:
        return pid
    elif pid == 0:
        return 0
    else:
        raise


pid = create_child()
if pid == 0:
    signal.signal(signal.SIGTERM, signal.SIG_IGN)
    while True:  # 子进程死循环打印字符串
        print 'in child process'
        time.sleep(1)
else:
    print 'in father process'
    time.sleep(5)  # 父进程休眠5s再杀死子进程
    os.kill(pid, signal.SIGTERM)  # 发一个SIGTERM信号
    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在
    os.kill(pid, signal.SIGKILL)  # 发一个SIGKILL信号
    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在

Мы устанавливаем функцию обработчика сигнала в дочернем процессе, SIG_IGN означает игнорировать сигнал. Мы обнаружили, что после первого вызова os.kill дочерний процесс продолжит выводить данные. Указывает, что дочерний процесс не был убит. После второго os.kill подпроцесс окончательно перестал выводить.

Затем мы переходим к пользовательской функции обработки сигналов.После того, как дочерний процесс получает SIGTERM, он печатает предложение и затем завершает работу.

# coding: utf-8

import os
import sys
import time
import signal


def create_child():
    pid = os.fork()
    if pid > 0:
        return pid
    elif pid == 0:
        return 0
    else:
        raise


def i_will_die(sig_num, frame):  # 自定义信号处理函数
    print "child will die"
    sys.exit(0)


pid = create_child()
if pid == 0:
    signal.signal(signal.SIGTERM, i_will_die)
    while True:  # 子进程死循环打印字符串
        print 'in child process'
        time.sleep(1)
else:
    print 'in father process'
    time.sleep(5)  # 父进程休眠5s再杀死子进程
    os.kill(pid, signal.SIGTERM)
    time.sleep(5)  # 父进程继续休眠5s观察子进程是否还存在

Вывод выглядит следующим образом

in father process
in child process
in child process
in child process
in child process
in child process
child will die

Функция обработки сигнала имеет два параметра, первый sig_num представляет целочисленное значение захваченного сигнала, а второй кадр не прост для понимания и редко используется. Он представляет информацию объекта кадра стека о работе Python при прерывании сигналом. Читателю, возможно, не нужно разбираться в глубине.

Многопроцессорный параллельный вычислительный экземпляр

Здесь мы используем многопроцессорный расчет pi PI. Для пи PI есть формула математического предела, мы будем использовать компанию для расчета пи PI.

Сначала используйте версию с одним процессом

import math

def pi(n):
    s = 0.0
    for i in range(n):
        s += 1.0/(2*i+1)/(2*i+1)
    return math.sqrt(8 * s)

print pi(10000000)

вывод

3.14159262176

Программа некоторое время работала, чтобы получить результат, но это значение очень близко к пи.

Далее мы используем версию с несколькими процессами, мы используем Redis для межпроцессного взаимодействия.

# coding: utf-8

import os
import sys
import math
import redis


def slice(mink, maxk):
    s = 0.0
    for k in range(mink, maxk):
        s += 1.0/(2*k+1)/(2*k+1)
    return s


def pi(n):
    pids = []
    unit = n / 10
    client = redis.StrictRedis()
    client.delete("result")  # 保证结果集是干净的
    del client  # 关闭连接
    for i in range(10):  # 分10个子进程
        mink = unit * i
        maxk = mink + unit
        pid = os.fork()
        if pid > 0:
            pids.append(pid)
        else:
            s = slice(mink, maxk)  # 子进程开始计算
            client = redis.StrictRedis()
            client.rpush("result", str(s))  # 传递子进程结果
            sys.exit(0)  # 子进程结束
    for pid in pids:
        os.waitpid(pid, 0)  # 等待子进程结束
    sum = 0
    client = redis.StrictRedis()
    for s in client.lrange("result", 0, -1):
        sum += float(s)  # 收集子进程计算结果
    return math.sqrt(sum * 8)


print pi(10000000)

Расчет суммы ряда разбиваем на 10 вычислений подпроцесса, каждый подпроцесс отвечает за 1/10 от суммы расчета, а промежуточные результаты расчета кидает в очередь redis, а затем родительский процесс ожидает завершения всех подпроцессов, а затем все данные в очереди объединяются для вычисления окончательного результата.

Вывод выглядит следующим образом

3.14159262176

Этот результат согласуется с результатом одного процесса, но затраченное время намного меньше.

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

Для чтения расширенных статей, связанных с python, обратите внимание на паблик-аккаунт «Code Cave».