You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

483 lines
22 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"cells": [
{
"cell_type": "markdown",
"id": "dfbf0c9c",
"metadata": {},
"source": [
"# Создание процессов #"
]
},
{
"cell_type": "markdown",
"id": "d3a89d3c",
"metadata": {},
"source": [
"На этой лекции мы поговорим про создание процессов именно на Python. Мы узнаем, как создать дочерний процесс, поговорим о том, как работает системный вызов `fork`, а также рассмотрим примеры создания процессов при помощи модуля `multiprocessing`.\n",
"\n",
"Процесс в операционной системе создается при помощи системного вызова `fork`. Давайте рассмотрим программу, которая создает дочерний процесс при помощи системного вызова `fork`. Я ее привел на слайде."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8d07b01",
"metadata": {},
"outputs": [],
"source": [
"# Создание процесса на Python\n",
"import time\n",
"import os\n",
"\n",
"pid = os.fork()\n",
"\n",
"if pid == 0:\n",
" # дочерний процесс\n",
" while True:\n",
" print(f\"child: {os.getpid()}\" )\n",
" time.sleep(5)\n",
" \n",
"else:\n",
" # родительский процесс\n",
" print(f\"parent: {os.getpid()}\")\n",
" os.wait()"
]
},
{
"cell_type": "markdown",
"id": "4bfa662c",
"metadata": {},
"source": [
"Импортируем пару модулей `time` и `os`, затем вызываем системный вызов `fork`. Системный вызов `fork` создает точную копию родительского процесса. Это означает, что вся память, все файловые дискрипторы и все ресурсы, которые были доступны в родительском процессе, будут целиком и полностью скопированы в дочернем процессе. То есть после того, как системный вызов `fork` отработал, с этого момента у нас два процесса в операционной системе. Единственное отличие заключается в том, что системный вызов `fork` в родительский процесс вернет `pid` дочернего процесса, а в дочернем процессе переменная `pid` будет равна нулю.\n",
"\n",
"Код, который я сейчас выделил, будет исполнен в дочернем процессе, а код, который находится за веткой `else`, будет исполнен в родительском процессе. Итак, в родительском процессе мы вызываем системный вызов `os.wait`, это еще один дополнительный системный вызов и он позволяет нам дожидаться завершения дочернего процесса созданного. А в дочернем процессе вна бесконечном цикле выводим `pid` нашего процесса, который создали, и спим пять секунд.\n",
"\n",
"Давайте попробуем выполнить этот код в консоли и поизучаем снова характеристики наших процессов созданных. Наш пример. Запускаем его."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1c7aebc9",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"parent: 19405\n",
"child: 19406\n",
"child: 19406\n",
"^C\n",
"Traceback (most recent call last):\n",
" File \"ex2.py\", line 15, in <module>\n",
"Traceback (most recent call last):\n",
" File \"ex2.py\", line 11, in <module>\n",
" os.wait()\n",
"KeyboardInterrupt\n",
" time.sleep(5)\n",
"KeyboardInterrupt\n"
]
}
],
"source": [
"! python ex2.py"
]
},
{
"cell_type": "markdown",
"id": "b139ad9e",
"metadata": {},
"source": [
"Итак, создалось два процесса, родительский и дочерний. Давайте попробуем поискать их в операционной системе. Переключимся в другую консоль при помощи известной нам команды `ps uax`. Найдем наш процесс."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c17304c7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n",
"mikhayl+ 19407 0.0 0.0 125020 5736 pts/8 S+ 13:53 0:00 python ex2.py\n",
"mikhayl+ 19408 0.0 0.0 125020 3632 pts/8 S+ 13:53 0:00 python ex2.py\n",
"mikhayl+ 19409 0.0 0.0 113128 1300 pts/9 Ss+ 13:56 0:00 /bin/bash -c ps aux | head -1; ps aux | grep ex2.py\n",
"mikhayl+ 19413 0.0 0.0 112660 920 pts/9 S+ 13:56 0:00 grep ex2.py\n"
]
}
],
"source": [
"! ps aux | head -1; ps aux | grep ex2.py"
]
},
{
"cell_type": "markdown",
"id": "6c806b8c",
"metadata": {},
"source": [
"Итак, мы видим процесс c `pid`'ом 19407, 19408. Это и есть наши созданные процессы. Можно отобразить результаты команды `ps` в иерархическом виде, чтобы посмотреть, какой из процессов родительский, а какой дочерний. Делается это при помощи дополнительного флага `f`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a465721e",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n",
"mikhayl+ 19414 0.0 0.0 113128 1304 pts/9 Ss+ 13:56 0:00 | | \\_ /bin/bash -c ps auxf | head -1; ps auxf | grep ex2.py\n",
"mikhayl+ 19418 0.0 0.0 112660 924 pts/9 S+ 13:56 0:00 | | \\_ grep ex2.py\n",
"mikhayl+ 19407 0.0 0.0 125020 5736 pts/8 S+ 13:53 0:00 | \\_ python ex2.py\n",
"mikhayl+ 19408 0.0 0.0 125020 3632 pts/8 S+ 13:53 0:00 | \\_ python ex2.py\n"
]
}
],
"source": [
"! ps auxf | head -1; ps auxf | grep ex2.py"
]
},
{
"cell_type": "markdown",
"id": "d2b2c77e",
"metadata": {},
"source": [
"Действительно видим наш родительский процесс и дочерний. Давайте посмотрим, что процессы делают. Для этого нам понадобится команда `strace` и дополнительные права."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "66c5163f",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"strace: Process 19408 attached\n",
"select(0, NULL, NULL, NULL, {0, 365190}) = 0 (Timeout)\n",
"write(1, \"child: 19408\\n\", 13) = 13\n",
"select(0, NULL, NULL, NULL, {5, 0}^C\n",
"strace: Process 19408 detached\n",
" <detached ...>\n"
]
}
],
"source": [
"! strace -p 19408"
]
},
{
"cell_type": "markdown",
"id": "207d0457",
"metadata": {},
"source": [
"Итак, мы видим, что наш созданный дочерний процесс делает системный вызов `write` и выводит информацию в стандартный поток вывода. Теперь узнаем, что делает родительский процесс."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "f95d20b7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"strace: Process 19407 attached\n",
"wait4(-1, ^C\n",
"strace: Process 19407 detached\n",
" <detached ...>\n"
]
}
],
"source": [
"! strace -p 19407"
]
},
{
"cell_type": "markdown",
"id": "25bd9db4",
"metadata": {},
"source": [
"Родительский процесс сделал системный вызов `wait`, и операционная система сама оповестит его о том, когда дочерний процесс завершится.\n",
"\n",
"Давайте остановимся немного еще раз на памяти в родительском и дочернем процессе и рассмотрим пример. Итак, у нас есть программа, мы объявили в ней переменную `foo`, присвоили ей значение \"bar\" и делаем системный вызов `fork`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7175183",
"metadata": {},
"outputs": [],
"source": [
"# Память родительского и дочернего процесса\n",
"import os\n",
"\n",
"foo = \"bar\"\n",
"\n",
"if os.fork() == 0:\n",
" # дочерний процесс\n",
" foo = \"baz\"\n",
" print(\"child:\", foo)\n",
" \n",
"else:\n",
" # родительский процесс\n",
" print(\"parent:\", foo)\n",
" os.wait()"
]
},
{
"cell_type": "markdown",
"id": "dbec21e8",
"metadata": {},
"source": [
"Вот этот код мы исполняем в дочернем процессе. После того, как отработал системный вызов `fork`, как я уже говорил, вся память целиком и полностью будет скопирована из родительского процесса в дочерний. То есть у нас переменная `foo` будет точно так же доступна в дочернем процессе. И у нее будет значение \"bar\", так как память была скопирована. Но если мы изменим значение `foo` в дочернем процессе, это никак не повлияет на родительский процесс, на переменную `foo`, которая была объявлена в родительском процессе. Если мы выведем в родительском процессе ее значение, то оно будет равно значению \"bar\"."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "fef004b6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"parent: bar\r\n",
"child: baz\r\n"
]
}
],
"source": [
"! python ex3.py"
]
},
{
"cell_type": "markdown",
"id": "280c49e6",
"metadata": {},
"source": [
"Итак, память целиком и полностью копируется. Если ее рассматривать, то это разные памяти. У дочернего процесса своя, у родительского процесса своя. То же самое относится и к файловым дискрипторам.\n",
"\n",
"Рассмотрим пример, который я привел на слайде."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36018536",
"metadata": {},
"outputs": [],
"source": [
"# Файлы в родительском и дочернем процессе\n",
"# $ cat data.txt\n",
"# example string1\n",
"# example string2\n",
"\n",
"import os\n",
"f = open(\"data.txt\")\n",
"foo = f.readline()\n",
"\n",
"if os.fork() == 0:\n",
" # дочерний процесс\n",
" foo = f.readline()\n",
" print(f\"child: {foo}\")\n",
" \n",
"else:\n",
" # родительский процесс\n",
" foo = f.readline()\n",
" print(f\"parent: {foo}\")"
]
},
{
"cell_type": "markdown",
"id": "9cbbb1a0",
"metadata": {},
"source": [
"Предположим, у нас есть небольшой файл. Я подготовил такой файл с двумя строчками `example string1` и `example string2`. Тоже так же импортируем системный модуль `os`. Открываем файл на чтение и читаем в переменную `foo` одну строчку. После того, как мы считали одну строчку, делаем системный вызов `fork`. После этого у нас создается точная копия родительского процесса и все файловые дискрипторы, вся память опять Если мы в дочернем процессе снова вызовем метод `readline` у файла или у объекта `f`, то мы прочитаем уже вторую строчку из этого файла. И снова это никак не повлияет на родительский процесс. В родительском процессе, если мы вызовем `readline`, то мы точно так же считаем вторую строчку.\n",
"\n",
"Итак, еще раз обращаю внимание, что не только память, но и файловые дискрипторы целиком и полностью копируются в дочернем процессе, когда мы делаем системный вызов `fork`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5407785f",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"parent: example string2\r\n",
"child: example string2\r\n"
]
}
],
"source": [
"! python ex4.py"
]
},
{
"cell_type": "markdown",
"id": "e4464c2d",
"metadata": {},
"source": [
"Все эти примеры носят обучающий характер, и обычно код с использованием системных вызовов `fork` немного сложнее. `fork` может вернуть ошибку, ее нужно проверять, и обычно в Python'е используют модуль `multiprocessing` для создания процессов. Рассмотрим пример."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d94def7",
"metadata": {},
"outputs": [],
"source": [
"# Создание процесса, модуль multiprocessing\n",
"from multiprocessing import Process\n",
"\n",
"def f(name):\n",
" print(f\"hello {name}\")\n",
" \n",
"p = Process(target=f, args=(\"Bob\",))\n",
"p.start()\n",
"p.join()"
]
},
{
"cell_type": "markdown",
"id": "d3b5b21d",
"metadata": {},
"source": [
"Для того, чтобы запустить процесс на Python необходимо импортировать класс `Process` из модуля `multiprocessing`, создать объект класса `Process`, передать ему в конструктор функцию, которую мы хотим исполнить в отдельном дочернем процессе и аргументы этой функции. После того, как мы создали объект, никакого процесса создано не будет. Процесс будет создан тогда, когда мы вызовем метод `start` нашего объекта. Вот здесь, внутри метода `start`, будет вызван системный вызов `fork` и исполнена наша функция `f` в отдельном процессе. Очень важно ожидать завершения всех созданных дочерних процессов. Для этого можно воспользоваться удобной функцией `join` для нашего объекта `p`. Как мы видим, системные вызовы `fork` и `wait` спрятаны внутри красивых оберток. Вообще, не в каждой операционной системе есть системный вызов `fork` и поэтому в `multiprocessing` все аккуратно сделано за вас. И не надо за это беспокоиться. Просто используем модуль `multiprocessing` для создания собственных дочерних процессов. Если мы исполним этот пример, то в стандартном выводе мы увидим строчку `hello Bob`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "d5a38c40",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hello Bob\r\n"
]
}
],
"source": [
"! python ex5.py"
]
},
{
"cell_type": "markdown",
"id": "b27125c7",
"metadata": {},
"source": [
"Существует также альтернативный метод создания процесса при помощи `multiprocessing` при помощи наследования."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "222e55c3",
"metadata": {},
"outputs": [],
"source": [
"# Создание процесса, модуль multiprocessing\n",
"from multiprocessing import Process\n",
"\n",
"class PrintProcess(Process):\n",
" def __init__(self, name):\n",
" super().__init__()\n",
" self.name = name\n",
"\n",
" def run(self):\n",
" print(f\"hello {self.name}\")\n",
" \n",
"p = PrintProcess(\"Mike\")\n",
"p.start()\n",
"p.join()"
]
},
{
"cell_type": "markdown",
"id": "48c2df7d",
"metadata": {},
"source": [
"Для этого мы объявляем свой класс, наследуемся от класса `multiprocessing Process`. В конструктор передаем нужные параметры для функции, которая должна быть запущена в дочернем процессе. И переопределяем метод `run`. В методе `run` мы реализуем код, вызываем функции дополнительные и используем все параметры. Дальше все стандартно. Создаем объект нашего класса `PrintProcess`, передаем туда параметры, вызываем метод `start`. Метод `start` вызовет `fork` и выполнит наш код в дочернем процессе. Для завершения дочернего процесса мы вызываем метод `join`. Если мы выполним этот код, то увидим вывод `hello Mike`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1be7cfff",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hello Mike\r\n"
]
}
],
"source": [
"! python ex6.py"
]
},
{
"cell_type": "markdown",
"id": "b7a2cbd1",
"metadata": {},
"source": [
"Очень важно ожидать завершения всех дочерних процессов, чтобы контролировать освобождение всех ресурсов.\n",
"\n",
"Итак, на этой лекции мы поговорили о том, как создать дочерний процесс, обсудили детали работы системного вызова `fork`, а также обсудили, как создать процесс при помощи удобных методов из модуля `multiprocessing`. Мы не говорили о вопросах обмена данными между процессами и синхронизации процессов. Мы рассмотрим эти вопросы на примере потоков."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}