<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Глава 9. Планировщик горутин on Go: Under the Hood</title>
    <link>/ru/part3concurrency/ch09sched/</link>
    <description>Recent content in Глава 9. Планировщик горутин on Go: Under the Hood</description>
    <generator>Hugo</generator>
    <language>ru</language>
    <atom:link href="/ru/part3concurrency/ch09sched/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>9.1 Задача планирования и модель GMP</title>
      <link>/ru/part3concurrency/ch09sched/model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/model/</guid>
      <description>&lt;h1 id=&#34;91-задача-планирования-и-модель-gmp&#34;&gt;9.1 Задача планирования и модель GMP&lt;/h1&gt;&#xA;&lt;p&gt;Достаточно написать &lt;code&gt;go f()&lt;/code&gt; — и горутина начинает выполняться. За этой единственной строкой стоит самый сложный механизм рантайма Go: планировщик (шедулер). Он должен ответить на вопрос, который совсем не прост: каким образом десятки тысяч горутин могут поочерёдно использовать небольшое число ядер процессора, работая быстро и оставаясь при этом практически незаметными для пользователя. В этом разделе сначала формулируется задача, которую должен решить планировщик, описывается его общая структура и место среди других конкурентных рантаймов. Последующие разделы подробно рассматривают каждый компонент.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.2 Планирование с кражей работы</title>
      <link>/ru/part3concurrency/ch09sched/steal/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/steal/</guid>
      <description>&lt;h1 id=&#34;92-планирование-с-кражей-работы&#34;&gt;9.2 Планирование с кражей работы&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt; оставил нам вопрос: у каждого P есть собственная локальная очередь, а значит, работа неизбежно распределяется неравномерно. Одни P перегружены, другие простаивают. Как распределить нагрузку, не создавая центрального узкого места, — это основная трудность конкурентного планирования. Ответ Go — схема с тридцатилетней теоретической базой, воспроизводимая во всей отрасли: кража работы (work stealing).&lt;/p&gt;&#xA;&lt;p&gt;Этот раздел несколько глубже остальных. Сначала мы проясним, что именно делает Go, затем проследим стоящую за этим теорию планирования (почему кража работы «доказуемо хороша»), далее рассмотрим различные воплощения этой идеи в системах вроде Cilk, Java и Rust и, наконец, остановимся на вопросах, которые остаются открытыми.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.3 Модель MPG и единицы конкурентного планирования</title>
      <link>/ru/part3concurrency/ch09sched/mpg/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/mpg/</guid>
      <description>&lt;h1 id=&#34;93-модель-mpg-и-единицы-конкурентного-планирования&#34;&gt;9.3 Модель MPG и единицы конкурентного планирования&lt;/h1&gt;&#xA;&lt;p&gt;Первый вопрос, на который должен ответить шедулер, — не «как планировать», а «что планировать». Go называет объект планирования горутиной и реализует её на тройке M, P и G. Прежде чем перейти к алгоритму планирования (начиная с &lt;a href=&#34;.././schedule&#34;&gt;9.4&lt;/a&gt;), этот раздел разбирает три единицы планирования: что такое горутина в контексте истории информатики, как кодируется её контекст выполнения, почему само планирование должно происходить на специальном g0, через какие состояния проходит горутина за свою жизнь и как поток M, несущий её, приостанавливается и возобновляется. Когда эти понятия прояснятся, все последующие алгоритмы планирования сведутся к «перемещению G между этими единицами».&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.4 Цикл планирования</title>
      <link>/ru/part3concurrency/ch09sched/schedule/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/schedule/</guid>
      <description>&lt;h1 id=&#34;94-цикл-планирования&#34;&gt;9.4 Цикл планирования&lt;/h1&gt;&#xA;&lt;p&gt;Предыдущие разделы подготовили материал: мы знаем, что такое G, M и P (&lt;a href=&#34;.././mpg&#34;&gt;9.3&lt;/a&gt;), и знаем, как M находит работу (&lt;a href=&#34;.././steal&#34;&gt;9.2&lt;/a&gt;). Этот раздел запускает всё в движение — мы наблюдаем, как цикл планирования непрерывно выбирает горутины и выполняет их в рамках одного потока, и как он удерживает баланс между «позволить отдельной горутине чуть дольше занимать процессор» (пропускная способность и локальность) и «не позволить ни одной горутине оголодать» (справедливость).&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.5 Управление потоками</title>
      <link>/ru/part3concurrency/ch09sched/thread/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/thread/</guid>
      <description>&lt;h1 id=&#34;95-управление-потоками&#34;&gt;9.5 Управление потоками&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt; заложил трёхуровневую структуру GMP: G — единица выполнения на уровне пользователя, P — разрешение на планирование и носитель локальных ресурсов, а M — «нога», одолженная у операционной системы. Предыдущие разделы сосредоточились преимущественно на G и P; этот раздел переключает внимание на M и отвечает на ряд вопросов, откладывавшихся до сих пор: что такое M на самом деле, откуда он берётся, почему GOMAXPROCS ограничивает количество P, тогда как число потоков нередко оказывается больше, почему единственный блокирующий системный вызов не тормозит остальные G, и какую цену платит рантайм, когда пользователь хочет закрепить горутину за конкретным потоком (&lt;code&gt;LockOSThread&lt;/code&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.6 Обработка сигналов</title>
      <link>/ru/part3concurrency/ch09sched/signal/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/signal/</guid>
      <description>&lt;h1 id=&#34;96-обработка-сигналов&#34;&gt;9.6 Обработка сигналов&lt;/h1&gt;&#xA;&lt;p&gt;Сигналы операционной системы асинхронны и низкоуровневы: сигнал может прервать любой поток в произвольный момент, а набор действий, допустимых в обработчике сигнала, крайне ограничен. Типичный же запрос разработчика на Go — подключить канал к &lt;code&gt;SIGINT&lt;/code&gt; через &lt;code&gt;signal.Notify&lt;/code&gt; и выполнить корректное завершение по его приходу. Задача рантайма — выстроить мост между этими двумя реальностями: преобразовать непредсказуемый асинхронный сигнал в событие, которое горутина сможет безопасно обработать. Каждое проектное решение на этом мосту диктуется одним жёстким ограничением: что допустимо делать внутри контекста обработчика сигнала. Понимая это ограничение, всю остальную механику данного раздела можно воспринимать как её следствия.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.7 Кооперация и вытеснение</title>
      <link>/ru/part3concurrency/ch09sched/preemption/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/preemption/</guid>
      <description>&lt;h1 id=&#34;97-кооперация-и-вытеснение&#34;&gt;9.7 Кооперация и вытеснение&lt;/h1&gt;&#xA;&lt;p&gt;В &lt;a href=&#34;.././schedule&#34;&gt;9.5 Цикл планирования&lt;/a&gt; остался открытый вопрос: если некоторая G выполняется слишком долго, как остальные G всё равно смогут получить время процессора? Ответ неизбежно апеллирует к паре понятий из теории планирования — кооперативному и вытесняющему. Кооперативное планирование опирается на добровольную уступку управления планируемой стороной; вытесняющее — на прерывание планируемой стороны планировщиком извне.&lt;/p&gt;&#xA;&lt;p&gt;Рантайм Go не располагает возможностью аппаратного прерывания, подобной ядру операционной системы. Планировщик с перехватом работы (&lt;a href=&#34;.././steal&#34;&gt;9.2&lt;/a&gt;) по своей сути является кооперативным планированием в порядке поступления. Вопрос в том, как он всё же способен принудительно прерывать G, отказывающуюся уступать управление, не отступая при этом от данного принципа — именно это и предстоит прояснить в настоящем разделе. Отправной точкой служит теоретический вопрос: почему рантайм не может остановить горутину в произвольной инструкции?&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.8 Системный монитор</title>
      <link>/ru/part3concurrency/ch09sched/sysmon/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/sysmon/</guid>
      <description>&lt;h1 id=&#34;98-системный-монитор&#34;&gt;9.8 Системный монитор&lt;/h1&gt;&#xA;&lt;p&gt;Штатный путь планировщика описан в &lt;a href=&#34;.././schedule&#34;&gt;9.4 Цикл планирования&lt;/a&gt;: один M привязывается к одному P,&#xA;извлекает горутину из очереди, выполняет её, затем берёт следующую. Этот путь опирается на одно условие — что M вообще&#xA;получает возможность работать. Но стоит всем P застрять в длительных системных вызовах, или какой-либо горутине&#xA;уйти в бесконечный цикл и намертво удерживать P, — обычное планирование встаёт. Никто не забирает P обратно,&#xA;никто не опрашивает сеть. Иными словами, кооперативная логика, выполняемая на P, не способна справиться с ситуацией,&#xA;когда «сам P не может двигаться».&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.9 Опросчик сети</title>
      <link>/ru/part3concurrency/ch09sched/poller/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/poller/</guid>
      <description>&lt;h1 id=&#34;99-опросчик-сети&#34;&gt;9.9 Опросчик сети&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Исходные факты верифицированы по &lt;code&gt;src/runtime/netpoll.go&lt;/code&gt; и его платформенным реализациям&#xA;(&lt;code&gt;netpoll_epoll.go&lt;/code&gt;, &lt;code&gt;netpoll_kqueue.go&lt;/code&gt; и другим), а также по&#xA;&lt;code&gt;src/internal/poll/fd_unix.go&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Сетевой код Go выглядит блокирующим: &lt;code&gt;conn.Read&lt;/code&gt; просто «зависает» в ожидании данных.&#xA;Но если бы он действительно блокировал поток операционной системы, на котором выполняется,&#xA;то десять тысяч горутин, ожидающих данных из сети, удерживали бы десять тысяч потоков, и&#xA;модель M:N, тщательно выстроенная в &lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt;, мгновенно рухнула бы. Возможность&#xA;сохранить блокирующий стиль и при этом обеспечить масштабируемость — заслуга опросчика&#xA;сети (netpoller). За ним стоит долгая история «как обслуживать огромное число соединений&#xA;с помощью горстки потоков». Этот раздел сначала излагает эту историю и определяющие оси&#xA;проектирования, а затем рассматривает, как Go скрывает зрелый механизм событий внутри&#xA;рантайма: программист пишет синхронный код, а под капотом работает событийно-ориентированный&#xA;ввод-вывод.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.10 Таймеры</title>
      <link>/ru/part3concurrency/ch09sched/timer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/timer/</guid>
      <description>&lt;h1 id=&#34;910-таймеры&#34;&gt;9.10 Таймеры&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;time.Sleep&lt;/code&gt;, &lt;code&gt;time.After&lt;/code&gt;, &lt;code&gt;time.Timer&lt;/code&gt;, &lt;code&gt;time.Ticker&lt;/code&gt; и даже &lt;code&gt;SetDeadline&lt;/code&gt; для сетевого чтения и записи —&#xA;всё это опирается на одну и ту же инфраструктуру таймеров. Она должна отвечать на вопрос, который выглядит&#xA;простым, но на деле весьма тонок — вопрос о структурах данных: когда одновременно существуют тысячи таймеров,&#xA;как эффективно определить «кого нужно разбудить следующим, и когда», не выжигая при этом отдельный поток?&#xA;Этот раздел начинается с постановки абстрактной задачи, разбирает компромиссы различных решений и приходит к&#xA;выбору Go и его эволюции.&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.11 NUMA-осведомлённость и будущее планировщика</title>
      <link>/ru/part3concurrency/ch09sched/numa/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/ru/part3concurrency/ch09sched/numa/</guid>
      <description>&lt;h1 id=&#34;911-numa-осведомлённость-и-будущее-планировщика&#34;&gt;9.11 NUMA-осведомлённость и будущее планировщика&lt;/h1&gt;&#xA;&lt;p&gt;Планировщик, описанный в предыдущих разделах, опирается на предположение, которое нигде явно не&#xA;сформулировано: каждый M обращается к памяти с одинаковой скоростью, а стоимость перемещения G между&#xA;любыми двумя P одинакова. На ноутбуке или однопроцессорном сервере это предположение почти верно. Но&#xA;стоит запустить программу на крупном многопроцессорном сервере — оно начинает трещать, и чем больше ядер,&#xA;тем шире трещина. Этот раздел посвящён именно ей: откуда она берётся, почему планировщик Go так долго&#xA;её игнорировал, какой NUMA-осведомлённый дизайн был тщательно проработан, но так и не выпущен, и как&#xA;пользователи обходят проблему сегодня.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
