{
 "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
}
