{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "437382d2",
   "metadata": {},
   "source": [
    "# Глобальная блокировка интерпретатора #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "edf47592",
   "metadata": {},
   "source": [
    "На этой лекции мы поговорим о том, что такое глобальная блокировка интерпретатора, или, как ее сокращенно иногда называют, `GIL`.\n",
    "\n",
    "`GIL` очень тесно связан с выполнением потоков, и для более глубокого понимания того, как работают потоки в Python, нужны общие сведения о том, как устроен и как работает `GIL` в Python. Многие разработчики знают о `GIL` лишь то, что это какая-то штука, которая не позволяет одновременно двум потокам выполняться на одном ядре процессора, даже если этих ядер у процессора несколько. Тем не менее, `GIL` в первую очередь предназначен для защиты памяти интерпретатора от разрушений и делает все операции с памятью атомарными.\n",
    "\n",
    "Давайте рассмотрим следующую программу, которую я представил на слайде, и сразу запустим ее выполнение в консоли, потому что выполнение займет некоторое время."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "540fbcd9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "25.041810989379883\n",
      "71.44423460960388\n"
     ]
    }
   ],
   "source": [
    "# cpu bound programm\n",
    "\n",
    "from threading import Thread\n",
    "import time\n",
    "\n",
    "def count(n):\n",
    "    while n > 0:\n",
    "        n -= 1\n",
    "        \n",
    "# series run\n",
    "t0 = time.time()\n",
    "count(100_000_000)\n",
    "count(100_000_000)\n",
    "print(time.time() - t0)\n",
    "\n",
    "# parallel run\n",
    "t0 = time.time()\n",
    "th1 = Thread(target=count, args=(100_000_000,))\n",
    "th2 = Thread(target=count, args=(100_000_000,))\n",
    "\n",
    "th1.start(); th2.start()\n",
    "th1.join(); th2.join()\n",
    "print(time.time() - t0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddfd23f8",
   "metadata": {},
   "source": [
    "Итак, вот наш пример. Запускаем его. Можем посмотреть при помощи команды top, что этот пример потребляет определенное количество центрального процессора. Да, видим, действительно, наш интерпретатор потребляет почти 100% CPU на одном ядре."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5444e55f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[?1h\u001b=\u001b[H\u001b[2J\u001b[mtop - 15:30:28 up 415 days,  7:20,  1 user,  load average: 0.73, 0.33, 0.19\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n",
      "Tasks:\u001b[m\u001b[m\u001b[1m 314 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m   2 \u001b[m\u001b[mrunning,\u001b[m\u001b[m\u001b[1m 312 \u001b[m\u001b[msleeping,\u001b[m\u001b[m\u001b[1m   0 \u001b[m\u001b[mstopped,\u001b[m\u001b[m\u001b[1m   0 \u001b[m\u001b[mzombie\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n",
      "%Cpu(s):\u001b[m\u001b[m\u001b[1m  4.8 \u001b[m\u001b[mus,\u001b[m\u001b[m\u001b[1m  0.7 \u001b[m\u001b[msy,\u001b[m\u001b[m\u001b[1m  0.0 \u001b[m\u001b[mni,\u001b[m\u001b[m\u001b[1m 94.5 \u001b[m\u001b[mid,\u001b[m\u001b[m\u001b[1m  0.0 \u001b[m\u001b[mwa,\u001b[m\u001b[m\u001b[1m  0.0 \u001b[m\u001b[mhi,\u001b[m\u001b[m\u001b[1m  0.0 \u001b[m\u001b[msi,\u001b[m\u001b[m\u001b[1m  0.0 \u001b[m\u001b[mst\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n",
      "KiB Mem :\u001b[m\u001b[m\u001b[1m 65805548 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 23703044 \u001b[m\u001b[mfree,\u001b[m\u001b[m\u001b[1m  1669436 \u001b[m\u001b[mused,\u001b[m\u001b[m\u001b[1m 40433068 \u001b[m\u001b[mbuff/cache\u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n",
      "KiB Swap:\u001b[m\u001b[m\u001b[1m 33030140 \u001b[m\u001b[mtotal,\u001b[m\u001b[m\u001b[1m 33030140 \u001b[m\u001b[mfree,\u001b[m\u001b[m\u001b[1m        0 \u001b[m\u001b[mused.\u001b[m\u001b[m\u001b[1m 63027376 \u001b[m\u001b[mavail Mem \u001b[m\u001b[m\u001b[m\u001b[m\u001b[K\n",
      "\u001b[K\n",
      "\u001b[7m  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND     \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m\u001b[1m19526 mikhayl+  20   0  128124   6996   2748 R 100.0  0.0   0:05.20 python      \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m19506 mikhayl+  20   0  921208  45756   7872 S  11.8  0.1   3:14.42 python3     \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m15053 snaga     20   0  288136  37196   6792 S   5.9  0.1  16:41.20 uwsgi       \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m17818 mikhayl+  20   0  498940  69812   8296 S   5.9  0.1   4:34.57 jupyter-no+ \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m\u001b[1m19527 mikhayl+  20   0  157848   2212   1436 R   5.9  0.0   0:00.05 top         \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m    1 root      20   0  191788   4784   2420 S   0.0  0.0  20:00.79 systemd     \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m    2 root      20   0       0      0      0 S   0.0  0.0   0:04.01 kthreadd    \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m    3 root      20   0       0      0      0 S   0.0  0.0   0:00.62 ksoftirqd/0 \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m    5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:+ \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m    8 root      rt   0       0      0      0 S   0.0  0.0   0:00.05 migration/0 \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m    9 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh      \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m   10 root      20   0       0      0      0 S   0.0  0.0  20:58.21 rcu_sched   \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m   11 root      rt   0       0      0      0 S   0.0  0.0   2:53.71 watchdog/0  \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m   12 root      rt   0       0      0      0 S   0.0  0.0   2:42.51 watchdog/1  \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m   13 root      rt   0       0      0      0 S   0.0  0.0   0:01.13 migration/1 \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m   14 root      20   0       0      0      0 S   0.0  0.0   0:00.66 ksoftirqd/1 \u001b[m\u001b[m\u001b[K\n",
      "\u001b[m   16 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/1:+ \u001b[m\u001b[m\u001b[K\u001b[?1l\u001b>\u001b[25;1H\n",
      "\u001b[K"
     ]
    }
   ],
   "source": [
    "! top"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a282bd2",
   "metadata": {},
   "source": [
    "Давайте вернемся к программе и рассмотрим, что она делает. Прежде всего, у нас есть функция `count`, которая в цикле уменьшает значение счетчика до нуля. Нам необходимо выполнить два вызова этой функции со значением `100_000_000` и засечь, сколько времени займет выполнение двух этих функций с этим счетчиком. То есть функция потребляет только центральный процессор. Она не делает никаких операций ввода-вывода, не ходит в сеть. Для сравнения мы можем выполнить эту функцию в потоке. Создадим два потока при помощи уже известных нам ранее методов модуля `threading`. Передадим туда эту функцию, те же самые аргументы, запустим наши потоки, подождем, пока они завершатся при помощи метода `join`, и выведем количество секунд, которое было потрачено на выполнение работы этих двух потоков.\n",
    "\n",
    "Давайте вернемся в консоль и посмотрим на результаты работы наших функций. Мы видим, что параллельное выполнение при помощи потоков заняло больше времени. Как же так? Тогда зачем нужны потоки вообще, и почему так происходит?\n",
    "\n",
    "Всё дело как раз здесь в глобальной блокировке интерпретатора. Дело в том, что потоки при выполнении своего кода каждый раз получают блокировку интерпретатора. Если у нас задача `CPU bound`, так называют задачи, которые потребляют только процессор, то код, написанный с использованием тредов в Python, будет неэффективным. Он будет работать медленнее, чем код, который запущен последовательно. Тем не менее, если мы код нашей функции заменим, например, на задачу, которая требует операции ввода-вывода, то мы заметим большой прирост в итоговом времени выполнения, если сравнивать параллельное выполнение и выполнение в тредах."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be510455",
   "metadata": {},
   "source": [
    "```\n",
    "# как выполняется поток?\n",
    "\n",
    "a      r      a         r              a         r    a\n",
    "  run  |------|   run   |--------------|   run   |----| run\n",
    "------>|  IO  |-------->|      IO      |-------->| IO |----->\n",
    "       |------|         |--------------|         |----|\n",
    "a      r      a         r              a         r    a\n",
    "\n",
    "a - acquire GIL\n",
    "r - release GIL\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cc602c7",
   "metadata": {},
   "source": [
    "Если изобразить схематично, как выполняется поток, то выглядит это следующим образом. У нас есть поток, в котором выполняется наш Python код, и каждый раз Python интерпретатор пробует получить глобальную блокировку интерпретатора. Если Python выполняет операцию ввода-вывода или системный вызов, то он эту блокировку снимает, и далее выполнение происходит без блокировки. Поэтому если у нас таких будет потоков много, все задачи с вводом-выводом, с ожиданием завершения для операции ввода-вывода будут очень хорошо параллелиться. Это нужно учитывать в своих задачах, если вы будете применять потоки или процессы.\n",
    "\n",
    "`GIL` внутри реализован как обычная нерекурсивная блокировка, или объект класса `threading lock`. Все потоки спят пять миллисекунд в ожидании получения блокировки, и в Python 3, если работает один главный поток, то он не требует освобождения этой глобальной блокировки интерпретатора.\n",
    "\n",
    "Итак, на этой лекции мы обсудили, что такое `GIL` и какое он отношение имеет к потокам в Python. Так, Python потоки — это обычные потоки, или `POSIX threads`, но с ограничениями в виде глобальной блокировки интерпретатора. Все потоки выполняются с захватом `GIL`, но системные вызовы и операции ввода-вывода, для них `GIL` не нужен.\n",
    "\n",
    "Итак, мы рассмотрели вопросы про то, как работают потоки и процессы, и в следующих лекциях мы рассмотрим, как устроены сокеты и как работать с сетью с применением полученных знаний о потоках и процессах."
   ]
  }
 ],
 "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
}
