<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Многопоточность</title>
    <description>Статьи и обучающие пособия по созданию многопоточных приложений, а также информация о том, как во время работы избежать распространенных ошибок.</description>
    <link>https://tproger.ru/tag/multithreading</link>
    <atom:link href="https://tproger.ru/tag/multithreading/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Mon, 05 Oct 2026 00:47:50 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Многопоточность</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Producer-Consumer на Java, Go и Rust: одна задача и три уровня решения</title>
      <link>https://tproger.ru/articles/producer-consumer-na-java-go-i-rust-odna-zadacha-i-tri-urovnya-r</link>
      <comments>https://tproger.ru/articles/producer-consumer-na-java-go-i-rust-odna-zadacha-i-tri-urovnya-r?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Tproger]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/producer-consumer-na-java-go-i-rust-odna-zadacha-i-tri-urovnya-r</guid>
      <description><![CDATA[<p>Три уровня решения задачи производителя и потребителя: BlockingQueue в Java, каналы в Go и очередь без блокировок на Rust. Разбираемся, какой уровень нужен вам.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/producer-consumer-na-java-go-i-rust-odna-zadacha-i-tri-urovnya-r">Producer-Consumer на Java, Go и Rust: одна задача и три уровня решения</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Golang]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Паттерны проектирования]]></category>
      <category><![CDATA[Потоки и блокировки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 08 Sep 2026 09:00:57 GMT</pubDate>
      <content:encoded><![CDATA[<p>Задача Producer-Consumer звучит обманчиво просто: одни потоки производят элементы, другие их разбирают, а между ними стоит буфер. На собеседовании её просят решить почти всегда, и почти всегда кандидат берётся писать координацию потоков руками.</p><p>Между тем у этой задачи есть три принципиально разных уровня решения, и выбор уровня определяется не языком, а требованиями к задержке. Разбираем все три: блокирующую очередь на Java, каналы и горутины в Go и очередь без блокировок на Rust.</p><p>Producer-Consumer — это схема, в которой производители и потребители работают независимо, обмениваясь данными через общий буфер конечной ёмкости. Ёмкость здесь работает главным механизмом защиты: именно она не даёт быстрым производителям завалить медленных потребителей.</p><p>Ручная координация через wait() и notifyAll() в блоках синхронизации даёт гонки и ложные пробуждения, а активное ожидание в цикле впустую жжёт процессор.</p><p>Готовая блокирующая очередь решает задачу целиком: при переполнении она сама останавливает производителя, обеспечивая обратное давление вместо переполнения памяти.</p><p>Горутины — это не потоки операционной системы, а лёгкие единицы, которые планировщик рантайма раскладывает по потокам, поэтому создавать их можно в количествах, немыслимых для системных потоков.</p><p>Три гарантии прогресса различаются строго: obstruction-free требует отсутствия помех, lock-free гарантирует прогресс системы в целом, wait-free — завершение каждой операции за ограниченное число шагов.</p><p>Отсутствие блокировок и использование атомарных операций сами по себе не дают wait-free: цикл повторных попыток без верхней границы — это lock-free, как бы его ни называли в описании библиотеки.</p><h2>Уровень первый: блокирующая очередь Producer-Consumer</h2><p>С этого уровня начинают почти все, и здесь же <a href="https://dev.to/machinecodingmaster/mastering-the-producer-consumer-pattern-in-java-lld-the-restaurant-kitchen-khn">делают три типовые ошибки</a>:</p><ul><li>Писать собственную логику блокировок через wait() и notifyAll() внутри синхронизированных блоков. Это приносит тонкие гонки и баги ложного пробуждения, когда поток просыпается без всякого уведомления.</li><li>Крутить активное ожидание в бесконечном цикле поверх коллекции, не рассчитанной на многопоточность. Процессор загружен постоянной проверкой размера очереди, а корректности всё равно нет.</li><li>Не предусмотреть обратного давления. Когда производители быстрее потребителей, буфер растёт, пока приложение не упрётся в нехватку памяти.</li></ul><p>Правильная ментальная модель здесь кухонная: повара выкладывают блюда на раздачу фиксированной вместимости, официанты забирают их по готовности. Раздача заполнена — повар ждёт, и это нормальная работа системы, а не сбой.</p><p>В Java всё это уже реализовано в стандартной библиотеке:</p><p>Вся координация потоков спрятана внутри реализации очереди, и явных блокировок в коде не остаётся вовсе. Метод put останавливает производителя при переполнении, и это и есть обратное давление, ради которого затевалась ограниченная ёмкость.</p><h2>Уровень второй: передача сообщений вместо общей памяти</h2><p>Go подходит к задаче с другой стороны. Вместо того чтобы защищать общий буфер, язык предлагает передавать данные между независимыми исполнителями. Запускается такой исполнитель одним словом:</p><p>Ключевое слово go перед вызовом запускает функцию конкурентно. Правда, у примера выше есть подвох: когда main завершается, программа выходит целиком, и горутина может не успеть отработать. Дожидаться её нужно явно:</p><h3>Где здесь, собственно, очередь</h3><p>Горутины сами по себе задачу производителя и потребителя не решают: WaitGroup лишь дожидается завершения, буфера и обратного давления в нём нет. Роль ограниченной очереди в Go играет буферизированный канал, и это прямой аналог кухонной раздачи из предыдущего раздела:</p><p>Поведение совпадает с блокирующей очередью один в один: отправка встаёт при переполнении, приём встаёт на пустом канале. Разница в акценте. В Java вы защищаете общий буфер, в Go передаёте значение из одной горутины в другую, и владение данными переходит вместе с ним.</p><h3>Почему это не просто потоки с коротким именем</h3><p>Горутина не является потоком операционной системы. Это лёгкая единица конкурентного исполнения, которой управляет рантайм языка: он раскладывает горутины по небольшому набору системных потоков сам. Отсюда и практическое следствие — можно оперировать очень большим числом конкурентных задач, не создавая и не сопровождая отдельный системный поток под каждую.</p><p>Здесь же полезно развести два понятия, которые постоянно путают. Конкурентность — это способ структурировать программу так, чтобы несколько задач продвигались независимо. Параллелизм означает фактическое одновременное исполнение на нескольких вычислительных ресурсах. Программа может быть конкурентной, и при этом ни одна пара её задач не выполняется буквально в один момент.</p><h2>Уровень третий: очередь без блокировок</h2><p>Третий уровень нужен, когда неприемлема сама возможность того, что поток уснёт на блокировке. Это низкая задержка в торговых системах, обработка звука, ядра планировщиков. И здесь начинается терминология, в которой путаются чаще всего.</p><h3>Три гарантии прогресса</h3><p><b>Obstruction-free.</b> Операция завершится, если в какой-то момент ей дадут поработать без помех со стороны других потоков. При непрерывных помехах прогресс не гарантирован вообще. Самая слабая из трёх гарантий.</p><p><b>Lock-free.</b> Гарантирует прогресс системы в целом: даже если один поток застрял, какой-то другой обязательно завершит свою операцию. Отдельный поток при этом может голодать сколь угодно долго, но система не встаёт целиком.</p><p><b>Wait-free.</b> Каждая операция завершается за ограниченное число шагов независимо от того, что делают остальные потоки. Ключевая формулировка здесь именно «ограниченное число шагов», а не «обычно быстро», «не использует блокировок» или «работает на атомарных операциях». Это формальное свойство, а не характеристика скорости.</p><p><b>Два неравенства, которые стоит запомнить:</b><br />Отсутствие блокировок не равно wait-free. Использование атомарных операций тоже не равно wait-free.</p><h3>Почему мьютекса недостаточно</h3><p>Очередь, обёрнутая в мьютекс, — это совершенно корректный многопоточный код на Rust. Но wait-free она не является: поток, захвативший блокировку, может быть снят с процессора планировщиком, и все остальные будут ждать его возвращения неограниченно долго. Для большинства задач это приемлемо, для систем с жёсткими требованиями по задержке уже нет.</p><h3>Атомарность и упорядочивание — разные вещи</h3><p>Процессор гарантирует, что атомарная операция неделима. Этого мало: нужно ещё, чтобы потребитель увидел записи, сделанные производителем <b>до</b> публикации флага. За это отвечает модель упорядочивания памяти.</p><p>Пара «освобождение и захват» создаёт отношение синхронизации: если потребитель увидел освобождающую запись, он гарантированно видит и все предшествовавшие ей записи. Это одна из центральных идей программирования без блокировок, и именно её упускают, заменяя мьютекс на атомарную операцию без всего остального.</p><h3>Кольцевой буфер и номера последовательности</h3><p>Практическая конструкция ограниченной очереди для нескольких производителей и нескольких потребителей строится на кольцевом буфере с двумя атомарными счётчиками логических позиций. Физический слот вычисляется как остаток от деления позиции на ёмкость, а при ёмкости, равной степени двойки, деление заменяется маской по битам.</p><p>Каждый слот хранит не только значение, но и номер последовательности, по которому определяется, какому логическому поколению этот слот сейчас принадлежит:</p><p>Тип UnsafeCell здесь означает ровно то, что написано на упаковке: обращаться к значению можно только из блока unsafe, компилятор проверку прекращает, и инвариант «в конкретный момент со слотом работает один поток» держится на номере последовательности, а не на системе типов.</p><p>Тип MaybeUninit здесь не для красоты: при ёмкости в 1024 элемента нужны 1024 места под значения, а не 1024 сконструированных объекта. Производитель получает уникальную позицию одним атомарным инкрементом, и никакого соревнования за неё не возникает:</p><p>Последние три строки стоит запомнить, потому что через абзац они окажутся важны. Позиция достаётся производителю без соревнования, а вот дальше он крутится в ожидании, пока слот не перейдёт к его поколению. Верхней границы у этого ожидания нет.</p><h3>Где заканчивается честность в названиях</h3><p>Неприятная правда, которую стоит знать до выбора библиотеки: многое из того, что продаётся как wait-free очередь, на деле является lock-free. Причина в устройстве ядра алгоритма:</p><p>Операция обычно завершается быстро, но верхней границы на число неудачных попыток здесь нет, а именно она и определяет wait-free. И да, ровно такой цикл вы только что видели в нашей собственной очереди: строго говоря, на этом шаге она тоже lock-free, а не wait-free, и автор исходного разбора это прямо оговаривает. Строгая реализация требует ограниченной стратегии резервирования или механизма помощи: когда поток B встречает незавершённую операцию потока A, он не ждёт возвращения A, а доводит его операцию до конца сам. Так прогресс перестаёт зависеть от того, что планировщик сделал с конкретным потоком.</p><h2>Какой уровень выбрать</h2><p>Правило простое и почти всегда работает от обратного.</p><ul><li>Блокирующая очередь из стандартной библиотеки закрывает подавляющее большинство задач. Начинайте с неё и не пишите координацию потоков вручную.</li><li>Модель с передачей сообщений выигрывает, когда конкурентных задач много, а связи между ними хочется описать явно, а не через набор общих структур под блокировками.</li><li>Алгоритмы без блокировок берутся, когда есть измеренное требование к задержке, которое блокировка нарушает: не «хочется быстрее», а конкретная цифра в бюджете. И в продакшене их берут готовыми из библиотек вроде crossbeam, а разбирают по косточкам ради понимания механики.</li></ul><p>Ошибка почти всегда идёт в одну сторону: за атомарные операции берутся раньше, чем появилась причина, и получают код, который сложнее отлаживать, а гарантий даёт не больше.</p><h2>Что забрать с собой</h2><p>Задача Producer-Consumer решается на трёх уровнях, и уровень выбирается требованиями, а не вкусом. Ограниченный буфер с обратным давлением закрывает большинство случаев. Передача сообщений упрощает структуру, когда конкурентных задач становится много. Алгоритмы без блокировок нужны там, где блокировка нарушает измеренный бюджет задержки, и тогда важно понимать, какую именно гарантию вы покупаете.</p><p>Материалы разбора: <a href="https://dev.to/machinecodingmaster/mastering-the-producer-consumer-pattern-in-java-lld-the-restaurant-kitchen-khn">задача о ресторанной кухне на Java</a>, <a href="https://dev.to/ashitoshh01/why-does-go-have-goroutines-understanding-gos-lightweight-concurrency-341p">введение в горутины и модель конкурентности Go</a> и <a href="https://dev.to/derekmwale/building-a-wait-free-queue-in-rust-2329">построение wait-free очереди на Rust</a>.</p><p>Если в вашем коде есть класс, где потоки координируются через ручные ожидания и уведомления, у вас уже есть кандидат на замену стандартной очередью. Начните с него.</p>]]></content:encoded>
    </item>
    <item>
      <title>Async Rust: как уменьшить бинарник, пока rustc не оптимизирован</title>
      <link>https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan</link>
      <comments>https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan</guid>
      <description><![CDATA[<p>Разбираем, почему async Rust генерирует лишние state machine, как это бьёт по embedded и WASM, и что можно сделать в коде прямо сейчас.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan">Async Rust: как уменьшить бинарник, пока rustc не оптимизирован</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Низкоуровневое программирование]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Компиляторы]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 29 Jun 2026 10:21:42 GMT</pubDate>
      <content:encoded><![CDATA[<p>Асинхронный Rust обещал нам zero-cost abstraction: пишете почти как на обычном синхронном языке, а runtime сам разбирается с конкурентностью. На сервере с десятками гигабайт RAM это обещание обычно сбывается. Но стоит перейти к embedded-прошивке на 256 КБ flash или к WebAssembly-модулю, который гонится за каждым килобайтом, — и картина меняется. Функция с двумя await-точками превращается в state machine из 360 строк MIR, тогда как синхронный аналог укладывается в 23. Почему так происходит и что с этим делать уже сегодня?</p><p>Тема получила новый импульс после серии публикаций Диона Доктера (Dion Dokter) из голландской компании Tweede golf. Во второй части он заглядывает внутрь rustc и утверждает: async Rust так и не вышел из состояния MVP. Я не буду дословно переводить его рассуждения, а переосмыслю их с упором на практику: как понять, платите ли вы за async-удобство лишними байтами, и как снизить налог, пока компилятор не научился делать это сам.</p><ul><li>Асинхронный Rust компилируется в state machine: на каждую await-точку приходится отдельное состояние плюс служебные Unresumed, Returned и Panicked.</li><li>Простая функция с двумя await-точками даёт 360 строк MIR против 23 у синхронного кода. Часть излишков LLVM убирает, но не все, особенно при оптимизации по размеру.</li><li>Компилятор не схлопывает идентичные состояния, не инлайнит futures через await, генерирует state machine даже для async-блоков без await и оставляет panic-путь в состоянии Returned.</li><li>В release-сборках замена panic в Returned на возврат Pending может сократить прошивку на 2–5%. Убирание state machine у пустых async-блоков — ещё около 0,2%.</li><li>Пока rustc не исправлен, можно вручную: возвращать impl Future из «прозрачных» функций, использовать std::future::ready, схлопывать await-точки через match и передавать большие данные по ссылке.</li></ul><h2>Почему async-функция — это всегда state machine</h2><p>Когда вы пишете async fn, компилятор не просто оборачивает тело в callback. Он строит конечный автомат: enum, где каждый вариант — состояние между await-точками. Это называется coroutine desugaring и происходит на уровне MIR (Mid-level IR) — до того, как код попадает в LLVM.</p><p>Рассмотрим минимальный пример. Есть две функции, каждая из которых просто возвращает число, и третья складывает результаты:</p><p>У bar две await-точки, поэтому минимум два пользовательских состояния. Но если сдампить MIR после прохода coroutine_resume, картина шире:</p><p>Три служебных состояния добавляются всегда. Они нужны, чтобы соблюсти контракт Future::poll: опрос завершённой future не должен приводить к неопределённому поведению. Поэтому после первого Ready future переходит в Returned, а повторный poll паникует. Аналогично Panicked блокирует future после пойманного panic — похоже на poisoning мьютекса.</p><p>Логика корректная, но цена высокая: bar порождает 360 строк MIR, а эквивалентный синхронный код — 23. В 15 с лишним раз больше. LLVM часть этого выбросит, но на opt-level = "z" или в толстых async-графах вызовов он быстро сдаётся.</p><h2>Четыре места, где компилятор работает вхолостую</h2><h3>1. Panic в состоянии Returned</h3><p>Контракт future требует только отсутствия UB. Паниковать при повторном poll — не обязательно. Можно просто вернуть Pending снова: ничего опасного не произойдёт, а ветка panic — это лишний побочный эффект, который плохо оптимизируется.</p><p>Дион сделал экспериментальный патч rustc: в release-режиме Returned больше не паникует. На embedded-прошивках это дало <b>2–5% экономии бинарного размера</b>. В debug-сборках панику можно оставить, чтобы быстро ловить ошибочный повторный poll, — по аналогии с overflow-checks.</p><h3>2. State machine без await</h3><p>Взгляните на функцию без единой await-точки:</p><p>Идеальная реализация — всегда возвращать Poll::Ready(5) безо всякого enum. А rustc генерирует полноценный CoroutineLayout с тремя служебными состояниями и switch по дискриминанту. Такие функции часто появляются в трейтах, где один интерфейс должен быть async, но конкретная реализация ничего не ждёт. Патч, убирающий state machine у async-блоков без await, даёт ещё <b>0,2%</b> размера — мало, но правка тривиальная.</p><h3>3. Futures не инлайнятся через await</h3><p>Классический шаблон: адаптер просто пересылает вызов вниз.</p><p>Сейчас bar получает собственный state machine, который вызывает state machine foo. Вручную мы бы написали fn bar(blah) -&gt; impl Future { foo(blah) } и избавились бы от лишнего enum. Аналогично с преамбулой и постамбулой: их можно перенести на FutureExt::map из крейта futures.</p><h3>4. Одинаковые состояния не схлопываются</h3><p>Если в async-функции несколько веток match ведут к одинаковому await, компилятор создаёт отдельное состояние на каждую ветку:</p><p>Здесь send_response вызывается с разными аргументами, но структура состояний дублируется. Если вынести выбор аргумента за await, получится одно состояние вместо двух:</p><p>В одном примере из статьи MIR сокращается с 456 до 302 строк, а ассемблер — примерно на 11%. Оптимизации stack, поэтому выигрыш в реальном коде может быть заметнее.</p><h2>Что делать сегодня, не дожидаясь rustc</h2><p>Всё перечисленное выше — это работа для команды компилятора. Но релиз с этими патчами может занять месяцы, а то и годы. Пока он не вышел, можно снизить async-bloat в своём коде.</p><p><b>Главный принцип:</b> каждый лишний async fn — это лишний state machine. Если функция не содержит await, не делайте её async.</p><h3>Заменяйте async-пустышки на impl Future</h3><p>Типичный случай — трейт, где одна реализация реально ждёт ввода-вывода, а другая просто возвращает значение.</p><p>Вместо полноценного state machine получается готовая future, которая сразу возвращает Ready. Это особенно полезно в embedded-HAL, где трейты часто async, но конкретный драйвер может делать только прямой доступ к регистрам.</p><h3>Прозрачные обёртки без await</h3><p>Если функция только пересылает await вниз, уберите await:</p><h3>Схлопывайте await-точки</h3><p>Если несколько веток match заканчиваются одним и тем же await, вынесите выбор аргументов наружу. Это уменьшает число состояний state machine и облегчает жизнь LLVM.</p><h3>Передавайте большие данные по ссылке</h3><p>Async-функция захватывает в state machine всё, что живёт через await. Если передать массив по значению, он окажется внутри future целиком:</p><p>Разница в 52 раза по размеру future — и это без учёта memcpy, который компилятор вынужден вставлять при перемещении больших значений.</p><h2>Перспективы: Project Goal и финансирование</h2><p>Дион оформил эти идеи как <a href="https://rust-lang.github.io/rust-project-goals/2026/async-statemachine-optimisation.html" rel="noopener">Rust Project Goal</a> — формальный механизм, через который команды заявляют цели на полугодие. По его оценке, работа требует порядка <b>€30 000</b> финансирования. Для компиляторного проекта это скромная сумма: речь идёт о 2–5% размера прошивки практически в любом async-проекте.</p><p>Есть и другой подход к той же проблеме: не убирать state machine на входе, а научить LLVM лучше их оптимизировать на выходе. Дион считает, что оба направления дополняют друг друга. Чем проще state machine попадает в LLVM, тем эффективнее её можно проинлайнить и упростить на поздних проходах.</p><h2>Выводы</h2><blockquote>Async Rust never left the MVP state. The compiler generates state machines with a lot of unnecessary baggage. With a few targeted optimizations in rustc we can get smaller binaries and better performance for everyone.</blockquote><p>Утверждение «async Rust так и не вышел из MVP» звучит резко, но поясняет, почему наши «zero-cost» абстракции иногда всё-таки стоят дорого. Компилятор делает корректный, но не оптимальный код: лишние panic-ветки, неинлайненные futures, дублирующиеся состояния и state machine там, где они не нужны.</p><p>Для embedded и WASM это не абстрактная проблема, а конкретные килобайты прошивки. Пока rustc учится, разработчик может снизить налог вручную: убирать async у функций без await, возвращать impl Future из прозрачных обёрток, схлопывать await-точки и не передавать большие структуры по значению.</p><p>Если ваш проект на Rust бьётся о лимит flash, имеет смысл посмотреть на async-граф вызовов свежим взглядом. Часто проще убрать одну лишнюю async-обёртку, чем месяцами ждать патч в компиляторе.</p><p><b>Источники:</b><br />— <a href="https://tweedegolf.nl/en/blog/237/async-rust-never-left-the-mvp-state" rel="noopener">Dion Dokter, «Async Rust never left the MVP state»</a>;<br />— <a href="https://tweedegolf.nl/en/blog/235/debloat-your-async-rust" rel="noopener">Dion Dokter, «Debloat your async Rust»</a>;<br />— <a href="https://rust-lang.github.io/rust-project-goals/2026/async-statemachine-optimisation.html" rel="noopener">Rust Project Goal: Async state machine optimisation</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>JEP 533: в Java 27 Structured Concurrency меняет обработку исключений</title>
      <link>https://tproger.ru/news/jep-533-v-java-27-structured-concurrency-menyaet-obrabotku-isklyu</link>
      <comments>https://tproger.ru/news/jep-533-v-java-27-structured-concurrency-menyaet-obrabotku-isklyu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/jep-533-v-java-27-structured-concurrency-menyaet-obrabotku-isklyu</guid>
      <description><![CDATA[<p>JEP 533 в JDK 27: FailedException заменён на ExecutionException, Joiner получил третий тип-параметр, новый open()-оверлоад упрощает конфигурацию. Проверьте код.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/jep-533-v-java-27-structured-concurrency-menyaet-obrabotku-isklyu">JEP 533: в Java 27 Structured Concurrency меняет обработку исключений</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 14 May 2026 12:45:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>После семи preview Structured Concurrency в JDK 27 впервые избавляется от собственных preview-типов: FailedException заменён на стандартный ExecutionException. JEP 533 интегрирован в JDK 27 и приносит ещё два изменения: третий тип-параметр в Joiner и новый оверлоад open() — без явного Joiner при стандартной политике.</p><p>Structured Concurrency появилась в JDK 19 как инкубатор и прошла два инкубаторных раунда и несколько preview, постепенно сближая модель многопоточности с принципами структурного программирования: подзадачи живут строго внутри своей области видимости, отмена распространяется надёжно, а иерархии потоков отображаются в инструментах наблюдаемости.</p><ul><li><b>FailedException заменён на ExecutionException</b>: стандартные joiners теперь бросают ExecutionException вместо preview-специфичного FailedException — тот же тип, что и Future.get().</li><li><b>Третий тип-параметр R_X</b>: Joiner&lt;T, R, R_X&gt; выносит тип исключения join() в сигнатуру — контракт становится частью типа.</li><li><b>Новый open-оверлоад</b>: StructuredTaskScope.open(UnaryOperator&lt;Configuration&gt;) позволяет задать таймаут и имя без явной передачи Joiner.</li><li>Ранние access-сборки JDK 27 доступны с флагом --enable-preview.</li><li>Форма API, заложенная в preview 5, сохраняется — дизайн сходится к финализации.</li></ul><h2>FailedException уходит — приходит ExecutionException</h2><p>Главное изменение preview 7: joiners Joiner.allSuccessfulOrThrow(), anySuccessfulOrThrow() и awaitAllSuccessfulOrThrow() теперь бросают java.util.concurrent.ExecutionException вместо preview-специфичного FailedException. Причина исключения доступна через getCause() — так же, как в Future.get() со времён Java 5.</p><p>Смена типа сокращает разрыв между классическим и структурированным кодом. Привычный паттерн catch-switch переносится напрямую:</p><p>Команды, которые использовали FailedException в preview 5–6, должны заменить этот тип на ExecutionException при переходе на JDK 27.</p><h2>Третий тип-параметр: исключение становится частью типа</h2><p>StructuredTaskScope и интерфейс Joiner получают третий тип-параметр R_X, описывающий тип исключения, которое может бросить join(). Старая сигнатура Joiner&lt;T, R&gt; становится Joiner&lt;T, R, R_X&gt;.</p><p>Для прикладного кода, который использует стандартные joiners через open(), компилятор выводит все три параметра автоматически — исходный код выглядит так же, как раньше. Разница принципиальна для авторов кастомных joiners: предложение throws теперь становится частью типа, а не деталью реализации. Вызывающий код получает точный checked-exception контракт на join().</p><h2>Новый open-оверлоад: конфигурация без лишнего Joiner</h2><p>До preview 7 установить таймаут или имя области при политике по умолчанию (ждать всех, завершить со сбоем при первом падении подзадачи) требовало передачи явного Joiner рядом с оператором конфигурации. Теперь появился перегруженный StructuredTaskScope.open(UnaryOperator&lt;Configuration&gt;), который принимает только оператор конфигурации:</p><p>Оверлоад принимает UnaryOperator&lt;Configuration&gt; — то же более строгое типирование, которое было введено в preview 6. Политика join по умолчанию (ждать успеха всех или провала хотя бы одной подзадачи) сохраняется.</p><h2>Что осталось без изменений</h2><p>Структурные гарантии API остаются прежними: подзадачи наследуют привязки ScopedValue (JEP 506), JSON thread dump продолжает отображать иерархии областей для инструментов профилирования, StructureViolationException по-прежнему срабатывает при использовании области вне try-with-resources или форке из чужого потока.</p><p>Одно изменение JEP 533, выходящее за рамки трёх основных: метод onTimeout() интерфейса Joiner удалён и заменён на timeout(). Новый метод либо возвращает результат, либо при тайм-ауте бросает исключение с CancelledByTimeoutException как причиной. Авторам кастомных Joiner-реализаций потребуется переименовать метод и скорректировать логику обработки тайм-аута.</p><p>Preview 7 не является редизайном. Форма API, заложенная в preview 5, сохраняется. Изменения сосредоточены на эргономике и типизации, а не на структуре. Сужающийся охват каждого preview — разумный индикатор того, что дизайн сходится. JEP 533 не указывает сроков финализации, но Structured Concurrency прошла два инкубаторных раунда и шесть preview — это значительный путь.</p><h2>Как попробовать</h2><p>Предложение доступно в ранних access-сборках JDK 27 с флагом --enable-preview. Обратная связь принимается через mailing-листы OpenJDK и продолжает влиять на API.</p><ul><li>Скачать early-access сборку JDK 27 с <b>jdk.java.net/27</b></li><li>Добавить --enable-preview при компиляции и запуске</li><li>Заменить FailedException на ExecutionException в catch-блоках</li><li>Обновить сигнатуры кастомных Joiner-реализаций до трёх тип-параметров</li></ul><h2>Выводы</h2><blockquote>Preview 7 не меняет форму API — он делает её честной. Тип исключения теперь в сигнатуре, а не в документации.</blockquote><p>Три изменения в JEP 533 — замена FailedException, третий тип-параметр и новый оверлоад open() — продолжают линию на сближение Structured Concurrency с идиомами стандартной библиотеки. Дизайн, по всем признакам, сходится: если вы следите за эволюцией Java-конкурентности, это хороший момент попробовать JDK 27 early-access и отправить обратную связь в OpenJDK.</p><p>Подробности — в тексте <a href="https://openjdk.org/jeps/533">JEP 533</a> и на страницах <a href="https://www.infoq.com/news/2026/05/jep-533-jdk-27/">InfoQ</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Математика или программирование? Какие знания нужны ML‑специалисту</title>
      <link>https://tproger.ru/articles/matematik-ili-programmist--chto-nuzhno-znat-ml-specialistu</link>
      <comments>https://tproger.ru/articles/matematik-ili-programmist--chto-nuzhno-znat-ml-specialistu?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Валерия Турчак]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/matematik-ili-programmist--chto-nuzhno-znat-ml-specialistu</guid>
      <description><![CDATA[<p>Интервью про ML: бустинг против нейросетей, работа с LLM, валидация и культура кода в команде</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/matematik-ili-programmist--chto-nuzhno-znat-ml-specialistu">Математика или программирование? Какие знания нужны ML‑специалисту</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Машинное обучение]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Инфраструктура]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 12 Nov 2025 14:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><i>Для Tproger поговорили с Александром Сербулом, руководителем ML‑направления в Битрикс24. Он рассказал, как не застрять на старте и почему в ML инженерная культура важнее модных нейросетей. Внутри — о том, когда градиентный бустинг лучше LLM, почему код нужно покрывать тестами, как работать с нехваткой данных и на что смотреть при выборе команды.</i></p><p>Если вы пишете код и начинаете разбираться с большими данными, здесь много идей, которые можно применить прямо сейчас.</p><p>Главный месседж — зрелый ML‑инженер это не коллекционер нейронок, а разработчик, умеющий проверять гипотезы, валидировать результаты и писать поддерживаемый код.</p><h2>Кем должен быть ML‑специалист — математиком или программистом</h2><p>Без математики в ML далеко не уйдёшь, но и без хорошего кода модели не взлетят. Ошибка в формуле или пробел в логике тестов может стоить недели отладки. Поэтому подход должен быть инженерным: писать чисто, добавлять аннотации типов, покрывать код тестами, следить за читаемостью.</p><h2>Сколько математики нужно в ML</h2><p>Без линейной алгебры, матриц, теории вероятностей и дифференцирования не получится даже понимать, что именно делает модель. Эти базовые знания можно освоить за полгода‑год, но важно не просто формулы выучить, а развить интуицию.</p><p>Инженеру нужно чувствовать, как данные проходят через слои сети, что происходит с тензорами и почему параметры меняются именно так. Это не про сложную теорию — скорее про понимание механики. Когда видишь матрицы не как набор чисел, а как систему преобразований, всё становится проще, и дебаг модели перестаёт быть сложной задачей.</p><h2>Как развиваться в ML и не застрять на старте</h2><p>По мнению Александра, главный способ расти в машинном обучении — постоянно разбирать чужие работы. Читать статьи, пробовать воспроизводить их, собирать сети, смотреть, как устроены архитектуры. Не стоит относиться к программированию как к самоцели. Код — инструмент, но смысл в том, чтобы понимать, что делает модель внутри.</p><blockquote>У многих разработчиков подход поверхностный: берут готовое решение, подают данные и ждут результата, но так не появляется понимания.</blockquote><p>Развитие начинается, когда ты сам задаёшь вопросы вроде «почему слой ведёт себя так», «как меняются веса», «что даёт нормализация». Именно через эти вопросы начинается путь к инженеру, а не к пользователю фреймворков и вайбкодингу.</p><h2>Что с большими языковыми моделями</h2><p>Хайп вокруг больших языковых моделей уже выдохся: корпорации гонятся за размерами, а остальные используют их как библиотеку. Это не плохо — эмбеддинги и промты действительно решают прикладные задачи, от резюмирования встреч до автозаполнения CRM.</p><p>Но инженеру важно понимать, что промт не заменяет модель, это просто способ задать контекст, как преднастройка перед задачей. За ней всё тот же набор весов и вероятностных расчётов. Ошибка — считать промты заменой инженерной работы.</p><p>LLM полезны там, где есть конкретный сценарий и метрики: если видно, что саммари экономит время менеджера или классификатор улучшает отклик в CRM, тогда инструмент оправдан.</p><blockquote>В остальных случаях стоит смотреть на ресурсы и задавать вопрос — нужна ли тяжёлая модель, если задачу решает бустинг или простая логистическая регрессия.</blockquote><h2>Какие методы сейчас реально работают</h2><p>По данным Александра, для бизнес‑задач нет универсального решения, но есть набор проверенных инструментов. <b>Если данные категориальные — лучше использовать градиентный бустинг</b>, например CatBoost или XGBoost. Эти методы часто точнее нейросетей и требуют меньше подгонки.</p><p>Хороший результат даёт <b>комбинация моделей.</b> Например, можно взять LLM для генерации эмбеддингов, а дальше подать их в логистическую регрессию или тот же бустинг. Такой гибрид работает быстрее и требует меньше ресурсов, но при этом сохраняет качество.</p><p>Если данных мало или вычислительных мощностей не хватает, помогает <b>байесовский подход.</b> Он хорошо ведёт себя на шумных и несбалансированных выборках, где другие методы не подходят или не работают. Отдельное направление — <b>оптимизация моделей.</b> Уменьшение моделей через compression позволяет запускать их на обычных серверах без потери точности — актуально для компаний без GPU‑кластеров.</p><h2>Когда ML действительно нужен</h2><p>Не любую задачу стоит решать через машинное обучение. Александр объясняет: если алгоритм можно описать правилами, лучше начинать с классики. Но когда формулы не работают — например, при обработке изображений или машинном переводе — подходит ML. Главное, чтобы были данные: сотни или тысячи пар примеров «правильно» и «неправильно». Без них обучение просто не из чего строить.</p><p>Если данных мало, лучше брать байесовские методы или простые классификаторы — они устойчивее и не требуют много ресурсов. То же самое касается бизнес‑кейсов, где результат критичен, а точность нужно проверять.</p><blockquote>Решение в пользу ML — это не вопрос нового стека, а вопрос наличия данных и понимания задачи. Если подход классических алгоритмов решает проблему быстрее и дешевле, значит, выбираем его.</blockquote><h2>Что делать, если данных мало</h2><p>Недостаток данных — это нормальное состояние, полных и сбалансированных выборок почти не бывает. Поэтому разработчику приходится проявлять креатив. Один из способов — генерировать синтетические данные. Например, создавать дополнительные примеры с небольшими изменениями, чтобы модель видела больше разнообразия.</p><blockquote>Полезно искать и дополнительные источники сигналов. Если пользователь бросил корзину в интернет‑магазине, это может быть прокси‑признак будущей покупки. Такие косвенные данные часто помогают вытащить модель на нужный уровень точности.</blockquote><h2>Где ML приносит реальную пользу бизнесу</h2><p>Александр приводит примеры из практики Bitrix24. <b>Один из самых ощутимых по эффекту — скоринг лидов и сделок.</b> Там используется логистическая регрессия и градиентный бустинг. Метод простой, но позволяет заметно повысить точность прогнозов и напрямую влияет на выручку.</p><p><b>Вторая история — классификация запросов техподдержки.</b> Решение построено на основе алгоритма шинглов и простой модели, оно работает стабильно и подходит для разных языков.</p><p>Алгоритм шинглов — это простой способ представить текст для последующей обработки или сравнения без участия сложных моделей. <br /><br /><i>Пример: текст "машина" при длине шингла 3 даёт набор шинглов: "маш", "аши", "шин", "ина". <br /></i><br />Дальше можно сравнивать эти множества и вычислять похожесть, например через коэффициент Жаккара. <br /></p><p>В Bitrix24 метод шинглов помогает системе одинаково понимать обращения пользователей на разных языках или с разными формулировками.</p><h2>Как проверить, что модель действительно работает</h2><p>Без тестирования и валидации никакая модель не считается готовой. <b>Первое, с чего нужно начинать, — сравнение с базовой линией.</b> Самый простой пример — случайный классификатор. Если ваша модель даёт результат не лучше рандома, значит, смысла в ней нет.</p><p><b>Дальше важно понимать метрики: precision, recall, F1 и другие.</b> Они показывают, на сколько точно модель попадает в нужные ответы и сколько ложных срабатываний допускает. Не стоит воспринимать метрики как формальности — от них зависит, будет ли решение полезно для бизнеса.</p><p><b>Ещё один критерий — устойчивость модели. </b>Александр советует проверять, насколько решение лучше простых эвристик, которые уже используются в компании.</p><blockquote>Если ML‑модель не выигрывает по скорости или точности, её не стоит внедрять. Тестировать нужно на реальных данных и регулярно пересматривать результаты, особенно если бизнес‑процессы меняются.</blockquote><h2>Какие инструменты и ресурсы использовать</h2><p>Основная экосистема машинного обучения сегодня крутится вокруг Python, и начинать логично с него. Хороший старт — <a href="https://realpython.com/">realpython.com</a>, где подробно объясняются основы языка и приёмы написания читаемого кода.</p><p>Но нужно развивать инженерные привычки. Александр рекомендует читать книги вроде <b>Effective Java Джошуа Блоха</b>, чтобы прокачивать культуру разработки и понимать многопоточность. Даже если вы работаете с Python, знания из других языков помогают писать лучше и думать о производительности.</p><p>Из библиотек стоит освоить <b>PyTorch или TensorFlow</b> для нейросетей и <b>scikit‑learn для классического ML</b>. При росте нагрузки полезно переписывать критичные части на <b>Java или Rust</b> — это уменьшает задержки и делает систему устойчивее.</p><h2>Как набраться практики и выбрать правильную команду</h2><p>Лучший способ вырасти в ML — попасть туда, где есть реальные данные и сильная инженерная культура.</p><blockquote>Идите в крупные компании вроде Яндекса, ВК, Сбера или X5 — там есть инфраструктура, процессы и опытные коллеги. Просто читать статьи и собирать pet‑проекты недостаточно, если не сталкиваешься с настоящими продуктами и их ограничениями.</blockquote><p><b>Главный признак хорошей команды — наличие тестов, QA и понятного кода.</b> Если в отделе этого нет, нужно уходить, иначе быстро потеряешь профессиональный уровень. ML уже давно не чудо‑технология, а инструмент. И успех зависит не от того, кто первым применил нейросеть, а от того, чья команда умеет держать систему в рабочем состоянии и проверять результаты каждый день.</p><p>В конце хочется спросить: как вы сами учились или учитесь ML — через курсы, проекты или книги? Делитесь своим опытом и мыслями в комментариях — обсудим, какие подходы к обучению и работе в ML работают сегодня.</p>]]></content:encoded>
    </item>
    <item>
      <title>Вышел Python 3.14. Что нового и насколько он стал быстрее?</title>
      <link>https://tproger.ru/news/vywel-python-3-14---naskolko-on-bystree-</link>
      <comments>https://tproger.ru/news/vywel-python-3-14---naskolko-on-bystree-?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Валерия Турчак]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/vywel-python-3-14---naskolko-on-bystree-</guid>
      <description><![CDATA[<p>Python 3.14 стал быстрее на 27%, получил free-threading без GIL и впервые полноценно раскрывает многопоточность для современных процессоров</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/vywel-python-3-14---naskolko-on-bystree-">Вышел Python 3.14. Что нового и насколько он стал быстрее?</a>»</p>]]></description>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Процессор]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 09 Oct 2025 09:17:31 GMT</pubDate>
      <content:encoded><![CDATA[<p><b>Python 3.14</b> вышел совсем недавно — 7 октября. А уже на следующий день разработчик Мигель Гринберг опубликовал результаты независимых тестов.</p><p>Главный итог — новая версия работает <b>примерно на 27% быстрее</b>, чем Python 3.13. Ключевое новшество — полноценная поддержка <b>free-threading</b> (многопоточности без глобальной блокировки GIL).</p><h2>Как тестировали</h2><p>В тестах участвовали версии CPython 3.9–3.14, а также PyPy 3.11, Node.js 24 и Rust 1.9.</p><p>Проверяли производительность на двух алгоритмах: рекурсивном вычислении чисел Фибоначчи и сортировке пузырьком. В однопоточном режиме Python 3.14 показал стабильный прогресс:</p><ul><li>В тесте Фибоначчи ускорение на <b>27%</b> — 6,4 секунды против 8,2 секунд у версии 3.13.</li><li>В сортировке пузырьком время сократилось <b>до 2,05 секунды</b> против <b>2,8 секунд</b>.</li></ul><h2>Революция free-threading</h2><p>Главный прорыв — <b>free-threading</b>, который наконец снимает системное ограничение GIL и раскрывает потенциал многоядерных процессоров.</p><p>В четырехпоточном тесте Фибоначчи скорость выросла <b>в три раза</b>, а в сортировке — <b>в два раза</b> относительно стандартной сборки.</p><h2>Сравнение с другими</h2><p>Стоит отметить, что PyPy 3.11 остается недосягаемым лидером — он быстрее CPython 3.14 почти в пять раз в рекурсии и в 18 раз при сортировке.</p><p>Node.js приблизился к PyPy в одном тесте, а Rust ожидаемо вырвался вперед — до 70 раз быстрее Python.</p><h2>Что это значит</h2><p>Python 3.14 — самая быстрая версия CPython на сегодня. Для проектов с интенсивными вычислениями free-threading дает ощутимый прирост. А вот JIT-режим пока остается экспериментальным — ускорения почти нет.</p><p>Если ваша команда может обновиться — <b>это стоит сделать</b>. Python 3.14 не просто быстрее: он впервые по-настоящему раскрывает многопоточность.</p>]]></content:encoded>
    </item>
    <item>
      <title>Язык Julia: что это и почему он популярен в научных вычислениях</title>
      <link>https://tproger.ru/articles/yazyk-julia--chto-eto-i-pochemu-on-populyaren-v-nauchnyh-vychisleniyah</link>
      <comments>https://tproger.ru/articles/yazyk-julia--chto-eto-i-pochemu-on-populyaren-v-nauchnyh-vychisleniyah?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Денис Кудерин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/yazyk-julia--chto-eto-i-pochemu-on-populyaren-v-nauchnyh-vychisleniyah</guid>
      <description><![CDATA[<p>Что такое язык Julia. Показываем сравнение языка Джулия с другими. Рассматриваем преимущества и основные нюансы ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/yazyk-julia--chto-eto-i-pochemu-on-populyaren-v-nauchnyh-vychisleniyah">Язык Julia: что это и почему он популярен в научных вычислениях</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Big Data]]></category>
      <category><![CDATA[Опрос]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Машинное обучение]]></category>
      <category><![CDATA[Искусственный интеллект]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Tor]]></category>
      <category><![CDATA[Data Science]]></category>
      <category><![CDATA[Анализ данных]]></category>
      <category><![CDATA[QA]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 11 Apr 2025 14:20:31 GMT</pubDate>
      <content:encoded><![CDATA[<p>Согласно <a href="https://www.tiobe.com/tiobe-index/">индексу TIOBE</a>, Julia входит в топ-50 самых актуальных языков программирования в 2025 году и занимает в рейтинге 34-ю строчку.</p><p>Julia получил признание благодаря универсальности, скорости, понятному синтаксису и множеству других достоинств, о которых мы расскажем в статье. Этот идеальный вариант для научных вычислений в любых отраслях — от анализа огромных массивов данных до расчетов прочности архитектурных объектов.</p><p>Узнаем, каковы особенности и преимущества языка программирования Julia, почему он популярен в научных вычислениях, чем отличается от других топовых языков и где применяется.</p><h2>Основные особенности языка Julia</h2><p>Язык программирования с романтическим женским именем был создан в 2012 году профессором Массачусетского технологического института Аланом Эдельманом в сотрудничестве с группой студентов. Инструмент изначально разрабатывался с прицелом на использование в научных вычислениях. По замыслу команды, язык должен был занять нишу, в которой находились MATLAB с его многочисленными клонами и R, а также совместить удобство и простоту Python с производительностью С++ и Fortran.</p><p>Растущая популярность Julia демонстрирует, что планы разработчиков были по большей части реализованы. И хотя этот язык еще продолжает развиваться и совершенствоваться, уже сейчас его возможности широко используются для сложных математических вычислений, анализа данных и машинного обучения. Последнее направление приобретает все большую актуальность, поскольку нейросети и технологии на основе ИИ внедряются во все нашей сферы жизни и нуждаются в корректном, эффективном и быстром обучении.</p><p>Julia действительно устраняет разрыв между высокоуровневыми интерпретируемыми и низкоуровневыми компилируемыми языками, демонстрируя высокую производительность без утраты простоты применения и продуктивности. В числе других достоинств языка — поддержка многопоточности и параллелизма.</p><p>Рассмотрим более подробно главные плюсы Julia.</p><h2>Скорость и производительность</h2><p>Скорость, которая напрямую определяет производительность, входит в число ключевых преимуществ языка. Показатель в первую очередь обусловлен наличием компилятора, работающего в формате Just-In-Time. Это позволяет создавать эффективный нативный код, который обеспечивает работу сложных алгоритмов на реальном оборудовании.</p><p>Как это реализуется на практике? Представьте, что вам нужно перемножить две огромные матрицы или промоделировать климат в Московской области на 50 лет вперед. На Python это может занять часы, на C — требует тонн низкоуровневого кода. Julia справляется с такими задачами почти так же быстро, как C, но с лаконичностью Python.</p><p>Julia не интерпретирует код построчно, как Python, а сразу компилирует его в эффективный машинный код, как это делает C. Это дает ускорение в 10–100 раз в сравнении с чисто интерпретируемыми языками.</p><p>Скорость особенно актуальна в  машинном и глубоком обучении. Ускоренная обработка обширных массивов данных и такие же быстрые сложные вычисления существенно ускоряют разработку инструментов на основе нейросетей.</p><p>Вот несколько фактов, чтобы вы не сомневались в производительности Julia:</p><ul><li>NASA использует Julia для моделирования полетов — код работает в 15 раз быстрее, чем предыдущая версия на Python + Cython.</li><li>В тестах линейной алгебры Julia обгоняет Python (NumPy) в 2–3 раза, а с нативными типами — почти догоняет C.</li><li>Пакет DifferentialEquations.jl решает сложные уравнения в 100 раз быстрее SciPy (Python).</li></ul><ul><li>Обучение нейросетей (Flux.jl) ускоряется в 3–5 раз против Python + TensorFlow/PyTorch.</li><li>Анализ геномов в биоинформатике занимает минуты вместо часов.</li></ul><p>При этом скорость Julia не наносит ущерба удобству применения.</p><h2>Гибкость</h2><p>Качество языка, которое часто называют «дружелюбием», позволяет юзерам без особых проблем осваивать и использовать Julia для решения самого широкого круга задач. Тем, кто знаком с Python или MATLAB, перейти на Джулию еще проще.</p><p>При этом высокоуровневый синтаксис позволяет выражать сложные алгоритмы минимальными средствами. Лаконичность языка делает его предельно доступным для изучения.</p><p>В Julia можно присваивать переменные, не объявляя их тип, при этом язык поддерживает все широко используемые алгоритмические структуры и способы хранения данных (словари, матрицы). Для работы со сложными типа данных есть бесплатные библиотеки.</p><h2>Мощная экосистема пакетов</h2><p>Для Julia создано огромное количество библиотек и фреймворков, существенно расширяющих ее функциональность.</p><p>Обратите внимание на эти инструменты:</p><ul><li>Flux, MLJ и Knet — написанные на Julia пакеты для глубокого обучения. Позволяют создавать многослойные нейросети и модели непосредственно на этом языке.</li><li>DataFrames.jl — набор фреймворков для работы с данными. Выполняет те же задачи, что и Pandas для Python. Включает полезные и высокоэффективные инструменты для управления данными и их анализа.</li><li>JuliaImages — пакет библиотек для работы с изображениями. Включает инструменты для загрузки, обработки и трансформации картинок.</li><li>Jump.jl — специализированный язык моделирования, интегрированный в Julia. Предназначен для математической оптимизации с использованием линейного, нелинейного и комбинированного программирования.</li></ul><p>Это лишь несколько примеров специальных пакетов. На практике можно найти десятки других инструментов в зависимости от поставленных задач.</p><h2>Поддержка многопоточности и параллельных вычислений</h2><p>Многопоточность обеспечивает языку Julia мощность без сложностей. Если требуется выполнить масштабные вычисления, можно задействовать все ядра процессора одновременно. Julia делает это настолько просто, что даже новичок сможет ускорить свой код в разы.</p><p>В отличие от Python, где многопоточность требует сложных библиотек, в Julia она встроена в сам язык. Чтобы запустить этот процесс, достаточно одной строки:</p><p>Что дает языку многопоточность «из коробки»:</p><ul><li>Ускорение в N раз (где N — число ядер). Например, на 8-ядерном процессоре тяжелый цикл выполнится почти в 8 раз быстрее.</li><li>Никаких заморочек с разделением памяти — Julia сама позаботится о корректности.</li></ul><p>Кроме того, Julia умеет распределять задачи даже между несколькими серверами. Это полезно в машинном обучении для параллельной обработки датасетов, в физическом моделировании, в финансовой сфере — обеспечивает моментальный анализ рисков для тысяч инвестиционных портфелей.</p><p>Благодаря параллелизму Джулия существенно опережает в скорости R/MATLAB. Даже в специализированных математических пакетах параллельные вычисления часто требуют сложной настройки. В Julia это 2–3 строки кода.</p><h2>Развитое сообщество</h2><p>Когда вы только начинаете работать с новым языком, важно знать, что у вас есть поддержка — и тут Julia выигрывает у многих конкурентов. За последние 10 лет вокруг языка сформировалось активное сообщество ученых, инженеров и разработчиков, которые не только пишут код, но и помогают новичкам.</p><p>Вот почему это важно:</p><ul><li>Быстрая помощь. На <a href="https://discourse.julialang.org/">официальном форуме</a> и в чатах вам ответят даже на базовые вопросы — без снисходительности, характерной для некоторых других языков.</li><li>Готовые решения. В <a href="https://juliapackages.com/">реестре пакетов</a> уже есть 7 000+ библиотек для всего — от машинного обучения до астрофизики.</li><li>Открытость. Создатели Julia сами участвуют в обсуждениях, а многие пакеты разрабатываются университетами (MIT, Stanford) и компаниями (NASA, IBM).</li></ul><p>Пример: если вы застряли с дифференциальными уравнениями, просто спросите в чате — и с вероятностью 90% вам ответит либо автор пакета DifferentialEquations.jl, либо кто-то, кто уже решил такую же проблему.</p><p>Сообщество Julia — это редкий случай, когда «экспертность» не означает «закрытость». Здесь ценят и новичков, потому что каждый когда-то начинал с “Hello World”.</p><h2>Почему Julia популярен в научных вычислениях</h2><p>Представьте язык, который понимает ваши математические формулы буквально с полуслова. Julia родился именно таким — как универсальный инструмент для ученых, уставших выбирать между «понятно» и «быстро».</p><p>Вот что делает его особенным:</p><ul><li>пишете, почти как в тетради: 2x + 3y вместо 2*x + 3*y;</li><li>получаете скорость как у Фортрана, без головной боли с компиляцией;</li><li>матричные операции летают в разы быстрее, чем в Python;</li><li>сложные дифференциальные уравнения решаются за пару секунд.</li></ul><p>Секрет успеха — в продуманной начинке:</p><ul><li>встроенные суперспособности для математики;</li><li>может растягивать вычисления на все ядра процессора;</li><li>легко подключает библиотеки Python, R и даже C.</li></ul><p>При этом Julia универсален: подходит биологам для анализа ДНК, физикам, моделирующим квантовые системы, архитекторам, вычисляющим прочностные параметры конструкций. Библиотека Flux.jl строит нейросети быстрее PyTorch, а DataFrames.jl обрабатывает гигабайты данных без тормозов.</p><p>Это не просто язык — это турбодвигатель для научных открытий. Хотите говорить с компьютером на языке математики? Julia станет вашим переводчиком.</p><h2>Сравнение Julia с другими языками</h2><p>Чтобы в полной мере оценить особенности языка Julia, сравним его с ближайшими конкурентами.</p><h2>Julia vs Python</h2><p>Python по праву считается универсальным языком программирования — он прост в освоении, обладает огромным количеством библиотек и поддерживается многомиллионным сообществом разработчиков. Однако когда речь заходит о сложных численных расчетах и высокопроизводительных вычислениях, Julia предлагает ряд неоспоримых преимуществ.</p><p>Главное достоинство Julia — его фокусировка на научных задачах. Этот язык создавался специально для работы с большими объемами данных и сложными математическими операциями. Хотя комьюнити Julia пока меньше python-сообщества, оно состоит преимущественно из специалистов в области Data science, физики и математического моделирования.</p><p>При этом Julia не исключает использование Python. Напротив, благодаря встроенным инструментам взаимодействия — таким как PyCall, разработчики могут комбинировать сильные стороны обоих языков. Использовать Julia для ресурсоемких вычислений, а Python — для других компонентов системы. Такой симбиоз позволяет достичь максимальной эффективности в научных проектах.</p><p>При этом Julia превосходит Python в скорости, имеет более удобный для математиков синтаксис. Параллелизм в Джулия встроенный — для Питона придется использовать сторонние библиотеки.</p><h2>Julia vs R</h2><p>R уже много лет остается верным помощником статистиков — его создавали специально для работы с данными, и в этом он действительно хорош. Но сегодня на сцену выходит Julia — современный язык, который сочетает удобство R с невероятной скоростью работы.</p><p>Те же операции с таблицами данных Julia выполняет в несколько раз быстрее. Если R справляется с задачей за минуту, Julia сделает это за 15-30 секунд. А когда дело доходит до сложных расчетов — разница становится еще заметнее.</p><p>Что действительно выделяет Julia:</p><ul><li>Это не просто язык для статистики, а полноценная платформа для научных вычислений.</li><li>Можно работать с искусственным интеллектом, физическими моделями и даже квантовыми вычислениями.</li><li>При этом сохраняется доступ ко всем привычным R-библиотекам.</li></ul><p>Для тех, кто только начинает погружаться в анализ данных, Julia — отличный выбор. Вам не придется сначала осваивать R для простых задач, а потом переучиваться на другие языки для сложных вычислений. Все есть в одном месте — от базовой статистики до работы с большими данными.</p><h2>Julia vs MATLAB</h2><p>MATLAB долгие годы был как дорогой швейцарский нож для ученых и инженеров — удобный, но привязанный к лицензии. А теперь представьте, что появился инструмент с теми же возможностями, но бесплатный и который можно модифицировать под свои нужды. Это Julia.</p><p>Скорость работы — первое, что замечают при переходе. Типичные расчеты в Julia идут на пятую-треть быстрее. А когда дело доходит до сложных проектов, разница становится еще заметнее.</p><p>Но главная магия — в экосистеме:</p><ul><li>7000+ бесплатных пакетов вместо платных тулбоксов;</li><li>возможность заглянуть «под капот» любого алгоритма;</li><li>сообщество, которое постоянно добавляет что-то новое.</li></ul><p>Синтаксис намеренно сделали похожим на MATLAB — переход ощущается как смена автомобиля той же марки на новую модель. Все знакомо, но едет быстрее и без ограничений по пробегу.</p><p>Для студентов это просто подарок — мощный инструмент без дорогой подписки. А для научных групп — возможность делиться кодом без оглядки на лицензии. Julia не просто догоняет MATLAB, она задает новые стандарты в научных вычислениях.</p><h2>Julia vs C++</h2><p>Если снова воспользоваться сравнением из автомобильной тематики, то C++ — это ручная коробка передач в мире программирования. Можно выжать максимум скорости, но каждая строчка кода требует ювелирной работы с памятью и указателями. Julia предлагает другой подход — это автоматическая трансмиссия, которая разгоняется почти так же быстро, но без головной боли.</p><p>Секрет Julia — умный переводчик (JIT-компилятор), который:</p><ul><li>в реальном времени превращает ваш код в машинные инструкции;</li><li>сам решает, как оптимально использовать память;</li><li>не заставляет вас ковыряться в низкоуровневых деталях.</li></ul><p>Разница особенно заметна в математических задачах. В Джулии не нужно приписывать все детали реализации в отличие от C++.</p><h2>Примеры кода на Julia</h2><p>Эти примеры демонстрируют все преимущества Julia в сфере математических вычислений.</p><h2>Вычисление факториала</h2><p>Это действительно просто:</p><p>Для больших чисел (n &gt; 20) стоит использовать factorial(big(n)), что автоматически переключает вычисления на длинную арифметику.</p><h2>Матричные операции</h2><p>Julia создан для работы с матрицами и линейной алгеброй. Вот несколько примеров, демонстрирующих его выразительность и производительность.</p><p>Базовые операции с матрицами:</p><p>Решение системы линейных уравнений:</p><p>Julia предоставляет удобные инструменты для параллельных вычислений. Вот как можно легко распараллелить задачи:</p><h2>Где применяется Julia</h2><p>Сферы использования Julia в науке, бизнесе, ИТ и других отраслях практически не ограничены. Рассмотрим наиболее актуальные направления.</p><h2>Научные исследования</h2><p>Julia активно используется в фундаментальных и прикладных науках благодаря своей скорости и удобству для математических расчетов:</p><ul><li>В физике с его помощью моделируют квантовые системы (например, в пакете QuantumOptics.jl) и решают дифференциальные уравнения (DifferentialEquations.jl).</li><li>В биоинформатике Julia применяют для анализа ДНК и белковых структур.</li><li>В астрономии — для обработки данных телескопов и симуляции галактик. Например, NASA использует Julia для расчета траекторий космических аппаратов.</li></ul><h2>Финансовые вычисления</h2><p>В финансах Julia ценят за высокую производительность при работе с большими массивами данных. Банки и брокеры применяют его для:</p><ul><li>Алгоритмического трейдинга — быстрого тестирования стратегий.</li><li>Риск-анализа — моделирования кризисных сценариев.</li><li>Оптимизации портфелей — решения задач линейной алгебры с миллионами переменных.</li></ul><p>Пакеты вроде JuliaQuant и Temporal.jl делают его отличной альтернативой Python (Pandas) и R.</p><h2>Искусственный интеллект и машинное обучение</h2><p>Хотя Python все еще доминирует в ML, Julia набирает популярность в этой сфере благодаря:</p><ul><li>скорости — обучение моделей в Flux.jl (аналог PyTorch) иногда в 2–3 раза быстрее;</li><li>гибкости — можно легко комбинировать нейросети с численными методами.</li></ul><p>Например, Julia используют для обработки изображений в реальном времени. Пакет MLJ объединяет сотни алгоритмов в едином интерфейсе.</p><h2>Анализ данных и обработка больших массивов информации</h2><p>Julia идеален для работы с Big Data:</p><ul><li>DataFrames.jl предоставляет инструменты, знакомые пользователям Pandas/R, но работает быстрее.</li><li>Поддержка многопоточности и распределенных вычислений (через интерфейс DistributedArrays.jl) позволяет обрабатывать терабайты данных.</li></ul><p>Компании используют Julia для анализа страховых рисков, а ученые — для обработки огромного количества данных, полученных в процессе исследований.</p><h2>Итоги</h2><p>Julia — это не просто язык для академиков. Он уже используется в реальной сфере везде, где важны скорость, точность и масштабируемость. Уже сейчас мы наблюдаем, как MATLAB-разработчики массово переходят на Julia, устав от закрытой экосистемы, а научные группы выбирают Julia для сложных вычислений там, где Python слишком медленный.</p><p>Тренд только набирает обороты. Добавляются новые функции типа встроенной поддержка GPU, а количество вакансий для разработчиков на Julia, особенно в сфере машинного обучения, постоянно растет.</p><p>Ты точно программист, если читаешь это! Больше мемов, инсайтов и боли кодеров <a href="https://t.me/+ajgz7pDecB4xZTI6">тут</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Grand Central Dispatch —  разбираемся раз и навсегда: Часть 1</title>
      <link>https://tproger.ru/articles/grand-central-dispatch---raz-i-navsegda-254703</link>
      <comments>https://tproger.ru/articles/grand-central-dispatch---raz-i-navsegda-254703?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Kiryl Famin]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/grand-central-dispatch---raz-i-navsegda-254703</guid>
      <description><![CDATA[<p>Эта статья подробно объясняет Grand Central Dispatch (GCD) в Swift (iOS), рассматривая потоки, очереди и задачи, а также синхронное и асинхронное выполнение, качество обслуживания (QoS) и deadlock. В первой части - терминология, во второй - разбор реальных задач.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/grand-central-dispatch---raz-i-navsegda-254703">Grand Central Dispatch —  разбираемся раз и навсегда: Часть 1</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Swift]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Приложение]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 06 Mar 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Привет! Меня зовут Кирилл Фомин, я iOS-разработчик.</p><p>В этой статье мы разберемся со Swift <b>Grand Central Dispatch (GCD)</b> раз и навсегда. Несмотря на то, что GCD может казаться устаревшим в условиях существования Swift Modern Concurrency, код с использованием этого фреймворка будет встречаться еще долгие годы как в проде, так и на собеседованиях.</p><p>Сегодня сосредоточимся только на фундаментальном понимании GCD. Мы в деталях рассмотрим самые ключевые концепции в многопоточности, включая взаимодействие потоков и очередей. С осознанием этих основ легко будет самостоятельно разобраться с другими темами: DispatchGroup, DispatchBarrier, семафоры, мьютексы и так далее.</p><p>Текст будет полезен как новичкам, так и опытным iOS-разработчикам. Я постараюсь объяснять все максимально понятным языком, избегая обилия терминов, но не упуская критичные для понимания моменты.</p><p>P.s. Это первая часть материала. В следующей рассмотрим реальные задачи, которые пригодятся вам на собеседованиях</p><h2>Основные понятия: поток, многопоточность, GCD, задача, очередь</h2><p><b>Поток</b> — фактически, контейнер, в который кладется и выполняется какой-то набор инструкций для системы. На самом деле, весь исполняемый код исполняется в каком-то потоке. Различают main (основной, главный) и worker потоки.</p><figure><img src="https://media.tproger.ru/user-uploads/113235/2025-03-03/71c514fa-25e6-459e-80d1-67ce2b702844.png" alt="" /><figcaption>Поток</figcaption></figure><p><b>Многопоточность</b> — возможность системы выполнять несколько потоков параллельно (одновременно).</p><p><b>Grand</b><b> </b><b>Central</b><b> </b><b>Dispatch</b><b> (</b><b>GCD</b><b>)</b> —  фреймворк для удобной работы с потоками (использования преимуществ многопоточности). Основные примитивы —  задача, очередь. GCD —  инструмент, позволяющий легко писать параллельно исполняющийся код. Простой пример: вынос тяжелых вычислений в отдельный поток, чтобы не мешать обновлению UI на основном (main) потоке.</p><p><b>Задача</b> —  набор инструкций, группируемых разработчиком. Тут важно понять, что разработчик сам решает, какой код будет относиться к конкретной задаче:</p><p><b>Очередь (</b><b>queue</b><b>)</b> —  основной примитив GCD. Это место, куда разработчик помещает задачи для выполнения. Очередь берет на себя задачу по распределению по потокам (каждая очередь имеет доступ к thread pool —  пулу потоков системы).</p><p>Фактически, очереди позволяют сфокусироваться на распределении задач по очередям, вместо ручного управления потоками. Когда задача ставится в очередь, она будет выполнена в свободном потоке, часто отличном от того, из которого была поставлена.</p><h2>Типы очередей</h2><p><b>Основная очередь</b> (main) — выполняется только в основном потоке. Она последовательная (об этом чуть позже).</p><p><b>Глобальные очереди (</b>global<b>)</b> —  5 очередей (на каждый уровень приоритетности), предоставляемых системой. Они параллельные.</p><p><b>Пользовательские очереди (</b>custom<b>)</b> — создаются пользователем. Разработчик сам выбирает один из 5 приоритетов и тип: последовательная или параллельная (по умолчанию —  последовательные).</p><p><i>* здесь создаем пользовательскую параллельную очередь, используя атрибут </i>.concurrent<i>.</i></p><h2>Приоритеты очередей</h2><p><b>Quality</b><b> </b><b>of</b><b> </b><b>Service</b> (QoS, дословно —  качество обслуживания) —  система приоритетов очередей. Чем выше приоритет очереди, в которой находится задача, тем больше ресурсов на нее будет выделено. Всего есть 5 QoS:</p><h3>.userInteractive</h3><p> Самый высокий приоритет. Используется для задач, требующих немедленного выполнения, но не подходящих для исполнения в основном потоке.</p><p>Например, в приложении, позволяющем ретушировать изображения в реальном времени, нужно мгновенно рассчитывать результат ретуши. Но если делать это в main потоке, затруднится обновление интерфейса и обработка жестов пользователя, которые всегда происходят в главном потоке (например, когда пользователь ведет пальцем по области, которую нужно отретушировать, и приложение должно сразу «под пальцем» показывать результат). Таким образом, мы получаем результат настолько быстро, насколько это возможно не в основном потоке.</p><h3>.userInitiated</h3><p>Приоритет для задач, требующих быстрой обратной связи, но не настолько критичной, как интерактивные задачи. Обычно используется тогда, когда пользователь понимает, что задача выполнится не мгновенно и придется подождать. Примером может послужить запрос на сервер.</p><h3>.default</h3><p>Стандартный приоритет. Присваивается, если разработчик не указал QoS при создании очереди —  когда нет специфичных требований к задаче, и приоритет не может быть определен из контекста (например, если вызвать задачу из очереди с приоритетом .userInitiated, она унаследует приоритет и также будет выполняться в очереди .userInitiated).</p><h3>.utility</h3><p>Приоритет для задач, не требующих обратной связи для пользователя, но необходимых для работы приложения. Например, синхронизация данных с сервером или запись автосохранения на диск.</p><h3>.background</h3><p>Для задач с самым низким приоритетом. Пример —  очистка кэша.</p><h2>Последовательные и параллельные очереди</h2><p>Все очереди делятся на 2 типа: последовательные (serial) и параллельные (concurrent).</p><p><b>Последовательные </b>(serial) <b>очереди</b> — когда следующая задача начинает выполняться только после окончания текущей.</p><p><b>Параллельные </b>(concurrent)<b> очереди</b> — когда следующая задача начинает выполнение сразу при выделении ресурсов, вне зависимости от статуса завершения предыдущих. Важно обратить внимание, что тут гарантируется только то, что задача, которая была поставлена на выполнение раньше, начнет свое выполнение раньше, но порядок окончания у нее —  любой.</p><figure><img src="https://media.tproger.ru/user-uploads/113235/2025-03-03/46c86d7e-0478-4e41-a288-84e4b23b00f2.png" alt="" /><figcaption>Последовательные и параллельные очереди</figcaption></figure><h2>Способы выполнения задач</h2><p>Важно заметить, что сейчас речь пойдет про способы выполнения задач относительно вызывающего потока. То есть, способ вызова отвечает за развитие событий в потоке, из которого мы добавляем задачу в очередь.</p><h3>Асинхронно (async)</h3><p>Вызов, при котором вызывающий поток не блокируется (то есть не ждет выполнения задачи, которую он добавляет в очередь).</p><p>В этом примере мы асинхронно добавляем задачу на print из главного <b>потока</b> (так как мы пишем этот код, не находясь ни в какой очереди, стандартно он исполняется в главном потоке) в основную <b>очередь</b>. Ставим print(“A”) в основную <b>очередь</b> и сразу же идем дальше, не ожидая ее исполнения —  выполняем print(“B”).</p><p>Поскольку главный <b>поток</b> занят выполнением текущего кода, а задачи из main <b>очереди</b> могут выполняться только в главном <b>потоке</b>, сначала завершается текущая задача —  print(“B”), а только потом, после освобождения главного <b>потока</b>, выполняется задача из основной <b>очереди</b> —  print(“A”). Таким образом, вывод —  BA.</p><p>Логика выполнения:</p><p>1.    Добавляем задачу в глобальную очередь с приоритетом .default из основного потока асинхронно — в вызывающем потоке сразу идем дальше и вызываем indicateLoading().</p><p>2.    Через какой-то промежуток времени система выделяет ресурсы для исполнения задачи в глобальной очереди и начинает ее выполнение в свободном потоке из thread pool. Вызывается метод updateData().</p><p>3.    Задача с updateInterface() добавляется в основную очередь асинхронно —  в вызывающем потоке не ждем ее исполнения и идем дальше.</p><p>4.    Когда добавляем задачи асинхронно, не можем точно сказать, когда на них будут выделены ресурсы. В конкретном случае неизвестно, что произойдет раньше: выполнение updateInterface() в основном потоке или Logger.log(.success) в одном из worker потоков из thread pool (как не можем и в шагах 1-2: первым выполнится indicateLoading() в основном или updateData() в worker). Основной поток хоть и не стоит без дела (в нем выполняется обновление интерфейса, обработка жестов и другие подкапотные задачи), но он работает всегда и на него выделено максимальное количество системных ресурсов. С другой стороны, выделение ресурсов для выполнения в workerthread может произойти почти мгновенно.</p><p><i>Заметьте, что в этом видео глобальная очередь выполняет свои задачи в каких-то свободных </i><i>worker</i><i> </i><i>потоках.</i></p><h3>Синхронно (sync)</h3><p>Вызов, при котором вызывающий поток останавливается и ожидает выполнения задачи, которую он добавил в очередь.</p><p>Здесь мы из <b>worker</b> потока, на котором выполнялась задача из глобальной очереди, синхронно ставим на выполнение увеличение баланса в пользовательской очереди. Текущий поток блокируется и ждет выполнения поставленной в очередь задачи. Таким образом, вывод баланса произойдет только после того, как завершится задача в пользовательской очереди на его увеличение.</p><p><i>Заметьте, что в этой анимации пользовательская очередь выполняет свои задачи в каких-то свободных </i><i>worker</i><i> </i><i>потоках.</i></p><h2>Deadlock</h2><p>Deadlock возникает, когда поток бесконечно ждет сам себя или другой поток для продолжения работы. Классический пример — вызов DispatchQueue.main.sync {} из главного потока.</p><p>В этом случае:</p><ul><li>Главный поток выполняет printing().</li><li>DispatchQueue.main.sync { print("B") } пытается выполнить код синхронно, но main-очередь может выполняться только в главном потоке.</li><li>Главный поток уже заблокирован и ждет завершения print("B"), но эта задача не может начаться, так как поток занят.</li></ul><p>В итоге возникает deadlock — выполнение программы зависает.</p><p><i>Заметьте, как </i>print(“B”)<i> из основной очереди не может выполниться, поскольку задача из </i><i>main</i><i> очереди может выполняться только на </i><i>main</i><i> потоке, но он заблокирован.</i></p><p>Освоение этих базовых концепций необходимо для создания производительных и стабильных приложений, а также для предотвращения распространенных ошибок по типу deadlock.</p><p>Надеюсь, Вы нашли для себя что-то полезное в этой статье. Если какие-то вещи остались непонятными, я могу бесплатно помочь с разбором: telegram <a href="http://t.me/kfamyn">@kfamyn</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Kotlin: как работают корутины и многопоточность? Уровень — middle</title>
      <link>https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle</link>
      <comments>https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Татьяна Жукова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle</guid>
      <description><![CDATA[<p>Проходи квиз, чтобы узнать, как хорошо ты шаришь в Котлине. С нас — почет и уважение</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kotlin--kak-rabotayut-korutiny-i-mnogopotochnost--uroven---middle">Kotlin: как работают корутины и многопоточность? Уровень — middle</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Android]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Kotlin]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 18 Jan 2025 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сегодняшний квиз не совсем для новичков. Многопоточность и корутины — уже скорее для уровня джун+/мидл. Хотя… Если вы настолько уверены в своих навыках, то попробуйте их проверить и доказать, что вы прирожденный Kotlin-разработчик. А если хотите немного освежить знания, читайте наши статьи:</p><ol><li><a href="https://tproger.ru/articles/kotlin-i-funkcionalnoe-programmirovanie--berite-luchwee">Kotlin и функциональное программирование: сделайте код лучше</a></li><li><a href="https://tproger.ru/articles/perehod-s-java-na-kotlin-pri-sozdanii-mobilnogo-prilozhenija">Переход с Java на Kotlin при создании мобильного приложения</a></li><li><a href="https://tproger.ru/translations/10-lajfhakov-dlja-android-razrabotchika-poleznye-extensions-na-kotlin">10 лайфхаков для Android-разработчика: полезные extensions на Kotlin</a></li></ol>]]></content:encoded>
    </item>
    <item>
      <title>Fish 4.0 — интерактивный Shell — переписали с C++ на Rust</title>
      <link>https://tproger.ru/news/--fish-4-0---interaktivnyj-shell---perepisali-s-c---na-rust</link>
      <comments>https://tproger.ru/news/--fish-4-0---interaktivnyj-shell---perepisali-s-c---na-rust?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/--fish-4-0---interaktivnyj-shell---perepisali-s-c---na-rust</guid>
      <description><![CDATA[<p>Бета-версия Fish 4.0, переписанная с C++ на Rust, предложила пользователям улучшенную многопоточность, безопасность и удобство</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/--fish-4-0---interaktivnyj-shell---perepisali-s-c---na-rust">Fish 4.0 — интерактивный Shell — переписали с C++ на Rust</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Безопасность]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[macOS]]></category>
      <category><![CDATA[Бета]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 30 Dec 2024 04:06:56 GMT</pubDate>
      <content:encoded><![CDATA[<p>Популярный интерактивный командный интерпретатор Fish выпустил бета-версию 4.0, полностью переписанную с языка C++ на Rust.</p><p>Основная цель перехода — улучшить многопоточность и повысить безопасность кода.</p><p>Разработчики отметили, что работа с C++ часто осложнялась инструментарием, разными компиляторами и необходимостью ручного управления потоками.</p><p>Rust предложил современные возможности и встроенные гарантии безопасности, которые сделали его естественным выбором для переписывания проекта.</p><h2>Новые возможности Fish 4.0</h2><p>Переход на Rust открыл двери для улучшений и новых функций. Среди ключевых нововведений в Fish 4.0:</p><ul><li>Обновленные привязки клавиш: Бинды стали более интуитивными, упрощая взаимодействие с терминалом.</li><li>Улучшенный поиск по истории: Теперь пользователи могут находить команды быстрее благодаря более мощным алгоритмам поиска.</li><li>Поддержка многопоточности: Благодаря Rust, Shell теперь эффективно обрабатывает несколько задач одновременно.</li></ul><p>Эти изменения делают Fish еще более удобным и современным инструментом для работы в командной строке.</p><h2>Как протестировать новую версию?</h2><p>Бета-версия Fish 4.0 доступна для тестирования. Установочные пакеты предоставлены для macOS, Ubuntu и других популярных дистрибутивов Linux.</p><p>Также доступны портативные бинарные файлы, которые можно запустить без установки.</p><p>Для тестирования пользователи могут перейти на<a href="https://fishshell.com/blog/rustport/"> официальный сайт проекта</a> и скачать соответствующую версию.</p>]]></content:encoded>
    </item>
    <item>
      <title>Межгалактическая команда. 4 серия</title>
      <link>https://tproger.ru/articles/mezhgalakticheskaya-komanda--4-seriya</link>
      <comments>https://tproger.ru/articles/mezhgalakticheskaya-komanda--4-seriya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дима Дмитриев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/mezhgalakticheskaya-komanda--4-seriya</guid>
      <description><![CDATA[<p>Спецпроект от Tproger и Мир Plat.Form. Подписывайтесь, голосуйте, как поступать главному герою, и конечно не пропускайте продолжение комикса.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/mezhgalakticheskaya-komanda--4-seriya">Межгалактическая команда. 4 серия</a>»</p>]]></description>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 28 Dec 2024 14:18:07 GMT</pubDate>
      <content:encoded><![CDATA[<p>Добро пожаловать в межгалактическую систему <a href="https://habr.com/ru/companies/nspk/articles/">Мир Plat.Form</a>! Вместе с новичком Эндрю вы узнаете, как работают потоки данных в системе, чем занимаются специалисты команды и что предстоит пройти новым сотрудникам на этапе обучения.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/8791405c-cbaf-4b26-85f1-d2652b8652ef.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/7a795e19-ce97-47d1-a0fe-969d51f8d9e6.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/0d2e2aff-2375-4c7c-b984-f022750d6ae8.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/d19b0c46-738e-4839-9321-6c7cda5ccfee.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/273c45ba-7c4b-4c79-80dc-07479cf78ad4.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/5d27d119-5e22-4447-b7d5-84ce7d3f3b8f.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/a0d95ed6-1234-4209-9543-ddced8ce5d81.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/e644d6b0-5faf-473e-b6aa-f50686d8d851.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/caec2ac4-a6c1-442f-bd59-566829518c0e.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/970d97a0-a6ef-402d-9c92-ddcb6c4ec3f9.png" alt="" /></figure>]]></content:encoded>
    </item>
    <item>
      <title>Межгалактическая команда. 3 серия</title>
      <link>https://tproger.ru/articles/mezhgalakticheskaya-komanda--3-seriya-253742</link>
      <comments>https://tproger.ru/articles/mezhgalakticheskaya-komanda--3-seriya-253742?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дима Дмитриев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/mezhgalakticheskaya-komanda--3-seriya-253742</guid>
      <description><![CDATA[<p>Спецпроект от Tproger и Мир Plat.Form. Подписывайтесь, голосуйте, как поступать главному герою, не пропускайте продолжение комикса</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/mezhgalakticheskaya-komanda--3-seriya-253742">Межгалактическая команда. 3 серия</a>»</p>]]></description>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 28 Dec 2024 11:36:32 GMT</pubDate>
      <content:encoded><![CDATA[<p>Добро пожаловать в межгалактическую систему <a href="https://tprg.ru/MhuK">Мир Plat.Form</a>! Вместе с новичком Эндрю вы узнаете, как работают потоки данных в системе, чем занимаются специалисты команды и что предстоит пройти новым сотрудникам на этапе обучения.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/2f3a8230-7e54-4924-a454-8f282a65538a.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/318908de-9e59-44e5-89a8-24adfb2c629a.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/181650a6-b139-48df-83e1-6c4fdd353cad.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/2d1f2e16-d1ef-40a1-b2d7-c3365b4b23f9.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/02a8d905-e32a-4d25-bcd4-c3aed9932c1c.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/040102e2-feae-4fa4-8a7e-2cbfefdaaead.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/8f2a5cdc-54b7-4adc-af4e-987695e9f98d.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/885513a1-1c81-4ce7-9d93-8d4c4c15a279.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/ce01805c-21b3-491c-b1eb-215b9b894d33.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/d32d92df-1a23-45da-aa8b-8c9d1dfb94cb.png" alt="" /></figure><p><i>Реклама. Рекламодатель: АО «НСПК» ИНН 7706812159, erid: 2W5zFJEadiG</i></p>]]></content:encoded>
    </item>
    <item>
      <title>Межгалактическая команда. 2 серия</title>
      <link>https://tproger.ru/articles/mezhgalakticheskaya-komanda-mir--2-seriya</link>
      <comments>https://tproger.ru/articles/mezhgalakticheskaya-komanda-mir--2-seriya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дима Дмитриев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/mezhgalakticheskaya-komanda-mir--2-seriya</guid>
      <description><![CDATA[<p>Спецпроект от Tproger и Мир Plat.Form. Подписывайтесь, голосуйте, как поступать главному герою, и не пропускайте продолжение комикса.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/mezhgalakticheskaya-komanda-mir--2-seriya">Межгалактическая команда. 2 серия</a>»</p>]]></description>
      <category><![CDATA[Интернет]]></category>
      <category><![CDATA[Тестирование]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 26 Dec 2024 13:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Добро пожаловать в межгалактическую систему Мир Plat.Form! Вместе с новичком Эндрю вы узнаете, как работают потоки данных в системе, чем занимаются специалисты команды и что предстоит пройти новым сотрудникам на этапе обучения.</p><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/d1666d73-a729-43cf-ac23-355a76d92f96.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/79cc96ed-78e9-4b3c-926d-935faac626a9.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/fb170b46-1757-47fa-91a1-18184f22bfc2.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/da4d84b6-e9cc-4f10-998b-dc08fed11d43.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/83609491-065a-4350-ad1f-30272cd076e6.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/da30dac1-b208-4f2b-b36b-6af3faad5970.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/1f4ba0a9-8158-45ba-88f2-a966bd03e09c.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/05bedc78-2fdc-4fef-871a-506bea0e3652.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/3d5d2a62-7905-490c-ae6c-a048d7945181.png" alt="" /></figure><figure><img src="https://media.tproger.ru/user-uploads/99633/2024-12-28/0701c879-1ce9-40df-b648-af869546e578.png" alt="" /></figure><p><i>Реклама. Рекламодатель: АО «НСПК» ИНН 7706812159, erid: 2W5zFHGtinc</i></p>]]></content:encoded>
    </item>
    <item>
      <title>Обзор фич LTS-релиза Java 21: в новый год с новой Java</title>
      <link>https://tproger.ru/news/novyj-post</link>
      <comments>https://tproger.ru/news/novyj-post?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Лунегов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/novyj-post</guid>
      <description><![CDATA[<p>Обзор фич релиза Java 21, который вышел в сентябре 2023. Возвращается золотой век Java-разработки.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/novyj-post">Обзор фич LTS-релиза Java 21: в новый год с новой Java</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 31 Jan 2024 14:32:21 GMT</pubDate>
      <content:encoded><![CDATA[<p>Давайте быстро пробежимся по основным фичам релиза Java 21, который вышел в сентябре 2023. Факт в том, что со временем переходить на него всё равно придется, нравится вам это или нет. Но большое количество новых интересных штук сделает этот переход более приятным. Большинство из этих изменений не годны для использования в продакшене, но вполне функциональны и позволяют вам оценить перспективу развития платформы. И задуматься вот о чем: возвращается золотой век, когда быть Java-разработчиком снова максимально актуально, интересно и полезно.</p><h2>Финализированные инструменты</h2><p>В Java 21 есть всего три полностью финализированных фичи. Это то, что можно использовать в продакшене прямо сейчас.</p><h3>Virtual Threads</h3><p>Виртуальные потоки — флагманская фича Java 21. Она позволяет запускать потоки в огромном количестве — тысячами, десятками тысяч. Это имеет под собой вполне конкретную практическую почву. Сетевой стек Java так странно написан, что на каждое сетевое соединение раньше нужно было выделять по целому отдельному “нативному” потоку операционной системы. Таких потоков нельзя сделать много, по факту, одна из практик — сделать их количество равным или чуть меньшим количеству ядер процессора вашего сервера. Очевидно, это ограничивает производительность любых сетевых приложений. И будем честными, приложения с веб-интерфейсом или веб-API — это то, чем занимается большинство джавистов.</p><p>Виртуальные потоки умеют совместно использовать один и тот же “нативный” тред операционной системы. Пока один виртуальный поток заблокирован задачей типа чтения из базы данных или из файла, его “потоком-носителем” могут пользоваться соседи и делать в нем какие-то вычисления. Типичное веб-приложение с CRUD-ами только тем и занимается, что ждёт ответа базы данных, поэтому эта оптимизация супер актуальна. Она легко может повысить отзывчивость приложения в целых два раза, и сделать его устойчивым и быстрым в тех случаях, когда раньше приходилось закупать дополнительные сервера.</p><p>Виртуальные потоки можно делать руками с помощью разнообразных функций стандартной библиотеки. Но куда важнее, что все основные фреймворки адаптировались под использование виртуальных тредов и создают их автоматически. Вам не нужно писать вообще никакого кода, ускорение приложений происходит магически. Всё, что вам нужно сделать — обновиться до Java 21, и перейти на самые свежие версии ваших веб-фреймворков (например, на Spring 3.2 и выше).</p><h3>Record Patterns</h3><p>Эта фича (JEP 440) позволяет избавиться от мусорного кода с разыменованием полей через instanceof. То, что раньше было написано вот так:</p><p>Теперь можно записать новым, компактным и устойчивым к ошибкам способом:</p><h3>Pattern Matching for switch</h3><p>Эта фича (JEP 441) позволяет делать паттерн-матчинг, похожий на то, что мы имеем в других языках (например, в Scala). Это позволяет писать более удобный и читаемый код, который часто используется при программировании алгоритмов обхода графов, в компиляторах, и так далее.</p><p>В Java 8 нам нужно было писать вот такой некрасивый код, который грозит развалиться при любой модификации:</p><p>В Java 21 для этого есть специальная синтаксическая конструкция:</p><h2>Новые возможности</h2><p>Некоторые возможности в языке продолжают улучшать на протяжении многих лет и релизов Java. В Java 21 из них только изменения в ZGC готовы для продакшена, все остальные относятся к preview-версиям фич на различных стадиях разработки.</p><ul><li>ZGC, учитывающий поколения (JEP 439). Основной масштабируемый сборщик мусора в JDK сейчас ZGC.<br />     В Java 21 он научился делить все объекты на поколения, и собирать их по отдельности. Поскольку большинство объектов умирают молодыми, а старые живут дольше, на сборку молодых уходит меньше ресурсов, и при этом высвобождается больше памяти. Это улучшает утилизацию памяти и процессора.</li><li>Улучшено API и произведены оптимизации производительности в двух основных “нативных” проектах, которые помогают в машинном обучении и работе с большими данными. Foreign Function &amp; Memory API (JEP 442) занимается нативным доступом к памяти и функциям нативных библиотек. Это позволяет удобно обращаться к быстрому C++ коду и работать с реальной быстрой оперативной памятью. Vector API (JEP 448) позволяет писать такой код, который компилятор потом сможет скомпилировать в векторные инструкции процессора, отчего этот код может начать выполняться быстрее (иногда — сильно быстрее!).</li><li>Улучшено API для двух новых проектов, посвященных работе с<br />     многопоточностью. Структурная многопоточность (structured concurrency, JEP 453) — это новая фундаментальная часть стандартной библиотеки, позволяющая писать хорошо понятный многопоточный код. Scoped Values (JEP 446) — улучшенная замена для thread locals. Они неизменяемые, не такие сложные и потребляют меньше памяти. Обе технологии специально спроектированы и хорошо работают вместе с инфраструктурой, предоставляемой<br />     виртуальными потоками.</li></ul><h2>Java 21 в России</h2><p>Как вы, наверное, знаете, зарубежные дистрибутивы типа Oracle JDK и других поставщиков уже недоступны в России из-за санкционных ограничений: их запрещено использовать на законодательном уровне. К счастью, у нас есть возможность продолжать использовать Java на проде с помощью <a href="https://axiomjdk.ru/">Axiom JDK</a>.</p><p>Кроме всего, что мы и так ожидаем от дистрибутива JDK для продакшена, Axiom JDK можно использовать в системах с требованием на присутствие JDK в Реестре Российского ПО, и на жесткие требования Критической Информационной Инфраструктуры (КИИ) по четвертому уровню доверия регулятора ФСТЭК. Благодаря этому, например, Axiom JDK сейчас используется для процессинга и <a href="https://corp.cnews.ru/news/line/2023-12-18_nspk_v_25_raza_uskorila_obrabotku">клиринга карт «Мир»</a> и в Системе Быстрых Платежей, то есть, более ста миллионов граждан России каждый день косвенно используют этот дистрибутив JDK.</p><p>У Java 21 впечатляющий срок поддержки: Oracle (на международном рынке) будет поддерживать ее до 2031 года, а Axiom JDK (в России) – на год больше, до 2032. То есть Java 21 с нами надолго.</p><h2>Заключение</h2><p>Java 21 — это новая “основная” версия Java, на которую рекомендуется обновляться всем. Прежде всего, из-за сроков поддержки, виртуальных тредов и изменений в веб-фреймворках (типа Spring Boot), которые их поддерживают.</p><p>Переход на Java 21, в большинстве случаев, довольно безболезненный и понятный. По крайней мере, если вы уже используете Java 11 и не обременены легаси времен Java 6 (в этом случае вам помогут инженеры Axiom JDK, они поддерживают старые версии). Начните использовать Java 21 (Axiom JDK 21 в России), обновите Spring и попробуйте запустить на нем свой проект — вполне возможно, результаты не заставят себя ждать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Параллелизм, многопоточность, асинхронность: разница и примеры применения (.NET, C#)</title>
      <link>https://tproger.ru/articles/parallelizm-mnogopotochnost-asinhronnost-raznica-i-primery-primenenija-net-c</link>
      <comments>https://tproger.ru/articles/parallelizm-mnogopotochnost-asinhronnost-raznica-i-primery-primenenija-net-c?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/parallelizm-mnogopotochnost-asinhronnost-raznica-i-primery-primenenija-net-c</guid>
      <description><![CDATA[<p>Давайте разберёмся, сколько программных моделей используют C#-разработчики и в чём их отличия. Особенности каждой модели рассмотрим подробно.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/parallelizm-mnogopotochnost-asinhronnost-raznica-i-primery-primenenija-net-c">Параллелизм, многопоточность, асинхронность: разница и примеры применения (.NET, C#)</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[.NET]]></category>
      <category><![CDATA[Гостевая публикация]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 23 Apr 2021 06:18:23 GMT</pubDate>
      <content:encoded><![CDATA[<p>Многие начинающие специалисты путают многопоточное, асинхронное и параллельное программирование. На первый взгляд, может показаться, что это одно и то же — но нет. Давайте разберёмся, сколько программных моделей используют C#-разработчики и в чём их отличия. Материал подготовлен совместно с Алексеем Гришиным, ведущим разработчиком DD Planet.</p><p>Существует несколько концепций: синхронное/асинхронное программирование и однопоточные/многопоточные приложения. Причём первая программная модель может работать в однопоточной или многопоточной среде. То есть приложение может быть: синхронным однопоточным, синхронным многопоточным и асинхронным многопоточным.</p><p>Отдельной концепцией считается параллелизм, который является подмножеством многопоточного типа приложений. Рассмотрим особенности каждой программной модели подробнее.</p><h2>Синхронная модель</h2><p>Потоку назначается одна задача, и начинается её выполнение. Заняться следующей задачей можно только тогда, когда завершится выполнение первой. Эта модель не предполагает приостановку одной задачи, чтобы выполнить другую.</p><h2>Однопоточность</h2><p>Система в одном потоке работает со всеми задачами, выполняя их поочерёдно.</p><figure><img src="https://media.tproger.ru/uploads/2021/04/1-27.jpg" alt="" /><figcaption>Однопоточная синхронная система</figcaption></figure><h2>Многопоточность</h2><p>В этом случае речь о нескольких потоках, в которых выполнение задач идет одновременно и независимо друг от друга.</p><figure><img src="https://media.tproger.ru/uploads/2021/04/2-4.png" alt="" /><figcaption>Многопоточная синхронная система</figcaption></figure><p>Пример такого концепта — одновременная разработка веб- и мобильного приложений и серверной части, при условии соблюдения архитектурных «контрактов».</p><p>Использование нескольких потоков выполнения — один из способов обеспечить возможность реагирования приложения на действия пользователя при одновременном использовании процессора для выполнения задач между появлением или даже во время появления событий пользователя.</p><h2>Асинхронность</h2><p>Характеристики асинхронного кода:</p><ul><li>обрабатывает больше запросов сервера, предоставляя потокам возможность обрабатывать больше запросов во время ожидания результата от запросов ввода-вывода;</li><li>делает пользовательский интерфейс быстрым, выделяя потоки для обработки действий в пользовательском интерфейсе во время ожидания запросов ввода-вывода, передавая затратные по времени операции другим ядрам ЦП.</li></ul><p>Если у системы много потоков, то их асинхронная работа выглядит примерно так:</p><figure><img src="https://media.tproger.ru/uploads/2021/04/3-2.jpg" alt="" /><figcaption>Многопоточная асинхронная система</figcaption></figure><h3>Конструкция async/await</h3><p>Для работы с асинхронными вызовами в C# необходимы два ключевых слова:</p><ul><li>async — используется в заголовке метода;</li><li>await — вызывающий метод содержит одно или несколько таких выражений.</li></ul><p>Они используются вместе для создания асинхронного метода. У асинхронных методов могут быть следующие типы возвращаемых значений:</p><ol><li>Task для асинхронного метода, который выполняет операцию, но не возвращает значение;</li><li>Task&lt;TResult&gt; для асинхронного метода, возвращающего значение;</li><li>void для обработчика событий;</li><li>начиная с версии 7.0 в языке C# поддерживаются любые типы с доступным методом GetAwaiter;</li><li>начиная с версии 8.0 в языке C# поддерживается интерфейс IAsyncEnumerable&lt;T&gt; для асинхронного метода, который возвращает асинхронный поток.</li></ol><p>Сама конструкция async/await появилась в C# 5.0 с выходом .NET Framework 4.5 и отчасти представляет собой синтаксический сахар. Механизм async/await не имеет реализации в CLR и разворачивается компилятором в сложную конструкцию на IL. Но эта конструкция — не сахар вокруг тасок, а отдельный механизм, использующий класс Task для переноса состояния исполняемой части кода.</p><p>Пример асинхронного метода:</p><figure><img src="https://media.tproger.ru/uploads/2021/04/4.jpg" alt="" /><figcaption>Результат асинхронного вычисления факториала</figcaption></figure><p>Этот пример приведён лишь для наглядности, особого смысла делать логику вычисления факториала асинхронной нет. Опять же, для имитации долгой работы мы использовали задержку на 8 секунд с помощью методы Thread.Sleep(). Цель была показать: асинхронная задача, которая может выполняться долгое время, не блокирует основной поток — в этом случае метод Main(), и мы можем вводить и обрабатывать данные, продолжая работу с ним.</p><h2>Параллелизм</h2><p>Эта программная модель подразумевает, что задача разбивается на несколько независимых подзадач, которые можно выполнить параллельно, а затем объединить результаты. Примером такой задачи может быть Parallel LINQ:</p><figure><img src="https://media.tproger.ru/uploads/2021/04/5.jpg" alt="" /><figcaption>Обзор архитектуры параллельного программирования в .NET</figcaption></figure><p>Еще один пример — вычисление среднего значения двумерного массива, когда каждый отдельный поток может подсчитать сумму своей строки, а потом объединить результат и вычислить среднее.</p><p>Однако не стоит забывать, что не все задачи поддаются распараллеливанию. Например, описанная выше задача по вычислению факториала, в которой на каждом последующем этапе нужен результат предыдущего.</p><h2>Какую программную модель выбрать?</h2><p>Перечисленные программные модели должны применяться в зависимости от задач. Их можно использовать как отдельно во всём приложении, так и сочетать между собой. Главное, чтобы приложение было максимально эффективным и удовлетворяло требования пользователя.</p><p>Если речь идет о сложных многопользовательских приложениях, то стремиться стоит к использованию асинхронной модели, так как важна интерактивность и отзывчивость интерфейса. Взаимодействие с пользователем в активном режиме всегда должно быть максимально эффективным, даже если в фоновом режиме в то же время выполняются другие задачи. Издержки асинхронности, например, на переключение исполняемого контекста, в таком случае нивелируются за счет общей эффективности приложения.</p><p>В разработке простых приложений, к примеру, парсера документа, необходимости в асинхронности, или даже многопоточности, может и не быть.</p>]]></content:encoded>
    </item>
    <item>
      <title>Десктопное приложение на Python: UI и сигналы</title>
      <link>https://tproger.ru/translations/desktopnoe-prilozhenie-na-python-ui-i-signaly</link>
      <comments>https://tproger.ru/translations/desktopnoe-prilozhenie-na-python-ui-i-signaly?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Олег Борисенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/desktopnoe-prilozhenie-na-python-ui-i-signaly</guid>
      <description><![CDATA[<p>Создание графического интерфейса на PyQt — фреймворке Qt, портированном с C++, на котором сделаны Telegram, VLC, VirtualBox и Jupyter Notebook.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/desktopnoe-prilozhenie-na-python-ui-i-signaly">Десктопное приложение на Python: UI и сигналы</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 20 Feb 2021 08:47:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>Считается, что Python не лучший выбор для десктопных приложений. Однако, когда в 2016 году я собирался переходить от разработки сайтов к программному обеспечению, Google подсказал мне, что на Python можно создавать сложные современные приложения. Например blender3d, который написан на Python.</p><figure><img src="https://media.tproger.ru/uploads/2021/02/blender.jpeg" alt="" /></figure><p>Мы будем использовать PyQt (произносится «Пай-Кьют‎»‎). Это фреймворк Qt, портированный с C++. Qt известен тем, что необходим C++ разработчикам. С помощью этого фреймворка сделаны blender3d, Tableau, Telegram, Anaconda Navigator, Ipython, Jupyter Notebook, VirtualBox, VLC и другие. Мы будем использовать его вместо удручающего Tkinter.</p><h2>Требования</h2><ol><li>Вы должны знать основы Python</li><li>Вы должны знать, как устанавливать пакеты и библиотеки с помощью <a href="https://tproger.ru/explain/pip-kak-ustanavlivat-pakety-v-python/">pip</a>.</li><li>У вас должен быть установлен Python.</li></ol><h2>Установка</h2><p>Вам нужно установить только PyQt. Откройте терминал и введите команду:</p><p>Мы будем использовать PyQt версии 5.15. Дождитесь окончания установки, это займёт пару минут.</p><h2>Hello, World!</h2><p>Создайте папку с проектом, мы назовём его helloApp. Откройте файл main.py, лучше сделать это vscode, и введите следующий код:</p><p>Этот код вызывает QGuiApplication и QQmlApplicationEngine которые используют Qml вместо QtWidget в качестве UI слоя в Qt приложении. Затем, мы присоединяем UI функцию выхода к главной функции выхода приложения. Теперь они оба закроются одновременно, когда пользователь нажмёт выход. Затем, загружаем qml файл для Qml UI. Вызов app.exec(), запускает приложение, он находится внутри sys.exit, потому что возвращает код выхода, который передается в sys.exit.</p><p>Добавьте этот код в main.qml:</p><p>Этот код создает окно, делает его видимым, с указанными размерами и заголовком. Объект Text отображается в середине окна.</p><p>Теперь давайте запустим приложение:</p><p>Вы увидите такое окно:</p><h2>Обновление UI</h2><p>Давайте немного обновим UI, добавим фоновое изображение и время:</p><p>Внутри типа ApplicationWindow находится содержимое окна, тип Rectangle заполняет пространство окна. Внутри него находится тип Image и другой прозрачный Rectangle который отобразится поверх изображения.</p><p>Если сейчас запустить приложение, то текст появится в левом верхнем углу. Но нам нужен левый нижний угол, поэтому используем отступы:</p><p>После запуска вы увидите следующее:</p><h2>Показываем текущее время</h2><p>Модуль gmtime позволяет использовать структуру со временем, а strftime даёт возможность преобразовать её в строку. Импортируем их:</p><p>Теперь мы можем получить строку с текущим временем:</p><p>Строка "%H:%M:%S" означает, что мы получим время в 24 часовом формате, с часами минутами и секундами (<a href="https://docs.python.org/3/library/datetime.html?highlight=strftime#strftime-and-strptime-format-codes">подробнее о strtime</a>).</p><p>Давайте создадим property в qml файле, для хранения времени. Мы назовём его currTime.</p><p>Теперь заменим текст нашей переменной:</p><p>Теперь, передадим переменную curr_time из pyhton в qml:</p><p>Это один из способов передачи информации из Python в UI.</p><p>Запустите приложение и вы увидите текущее время.</p><h2>Обновление времени</h2><p>Для того чтобы обновлять время, нам нужно использовать потоки. Для этого я предлагаю использовать сигналы.</p><p>Чтобы использовать сигналы нам нужен подкласс QObject. Назовём его Backend.</p><p>У нас уже имеется свойства для строки со временем curr_time, теперь создадим свойство backend типа QtObject в файле main.qml.</p><p>Передадим данные из Python в qml:</p><p>В qml файле один объект QtObject может получать несколько функций (называемых сигналами) из Python.</p><p>Создадим тип Connections и укажем backend в его target. Теперь внутри этого типа может быть столько функций, сколько нам необходимо получить в backend.</p><p>Таким образом мы свяжем qml и сигналы из Python.</p><p>Мы используем потоки, для того чтобы обеспечить своевременное обновление UI. Создадим две функции, одну для управления потоками, а вторую для выполнения действий. Хорошая практика использовать в названии одной из функций _.</p><p>Создадим pyqtsignal и назовём его updated, затем вызовем его из функции updater.</p><p>В этом коде updated имеет параметр arguments, который является списком, содержащим имя функции “updater”. Qml будет получать данные из этой функции. В функции updater мы вызываем метод emit и передаём ему данные о времени.</p><p>Обновим qml, получив сигнал, с помощью обработчика, название которого состоит из “on”  и имени сигнала:</p><p>Теперь нам осталось вызвать функцию updater. В нашем небольшом приложении, использовать отдельную функцию для вызова сигнала не обязательно. Но это рекомендуется делать в больших программах. Изменим задержку на одну десятую секунды.</p><p>Функция bootUp должна быть вызвана сразу же после загрузки UI:</p><h2>Всё готово</h2><p>Теперь можно запустить программу. Время будет обновляться корректно. Для того, чтобы убрать рамку, вы можете добавить в qml файл следующую строку:</p><p>Так должен выглядеть файл main.py:</p><p>Вот содержимое файла main.qml:</p><h2>Сборка приложения</h2><p>Для сборки десктопного приложения на Python нам понадобится pyinstaller.</p><p>Чтобы в сборку добавились все необходимые ресурсы, создадим файл spec:</p><h3>Настройки файла spec</h3><p>Параметр datas можно использовать для того, чтобы включить файл в приложение. Это список кортежей, каждый из которых обязательно должен иметь target path(откуда брать файлы) и destination path(где будет находится приложение).  destination path должен быть относительным. Чтобы расположить все ресурсы в одной папке с exe-файлами используйте пустую строку.</p><p>Измените параметр datas, на путь к вашей папке с UI:</p><p>Параметр console установим в false, потому что у нас не консольное приложение.</p><p>Параметр name внутри вызова Exe, это имя исполняемого файла. name внутри вызова Collect, это имя папки в которой появится готовое приложение. Имена создаются на основании файла для которого мы создали spec — main.py.</p><p>Теперь можно запустить сборку:</p><p>В папке dist появится папка main. Для запуска программы достаточно запустить файл main.exe.</p><p>Так будет выглядеть содержимое папки с десктопным приложением на Python:</p><p>О том, как использовать Qt Designer для создания UI приложений на Python читайте в <a href="https://tproger.ru/translations/python-gui-pyqt/">нашей статье</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Зачем нужно знать многопоточную разработку, даже если вы не собираетесь с ней работать</title>
      <link>https://tproger.ru/articles/zachem-nuzhno-znat-mnogopotochnuju-razrabotku-dazhe-esli-vy-ne-sobiraetes-s-nej-rabotat</link>
      <comments>https://tproger.ru/articles/zachem-nuzhno-znat-mnogopotochnuju-razrabotku-dazhe-esli-vy-ne-sobiraetes-s-nej-rabotat?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/zachem-nuzhno-znat-mnogopotochnuju-razrabotku-dazhe-esli-vy-ne-sobiraetes-s-nej-rabotat</guid>
      <description><![CDATA[<p>Java-разработчик Юрий Бабак объясняет, почему код почти всегда исполняется в многопоточном окружении и как эта скрытая сложность догоняет любого.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/zachem-nuzhno-znat-mnogopotochnuju-razrabotku-dazhe-esli-vy-ne-sobiraetes-s-nej-rabotat">Зачем нужно знать многопоточную разработку, даже если вы не собираетесь с ней работать</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 08 Dec 2020 09:16:52 GMT</pubDate>
      <content:encoded><![CDATA[<p>На последнем Joker у меня был разговор с коллегами из JUG’а на тему: «Все спрашивают про многопоточку, а задач с ней нет и научиться на практике негде». На самом деле, многопоточка есть почти всегда, особенно в Java-мире. Даже если нет никаких задач с ней явно связанных, почти всегда код будет работать в многопоточном окружении.</p><p>Меня зовут Юрий Бабак. Более десяти лет я занимаюсь разработкой на Java, а сейчас возглавляю команду разработки фронт-офисных решений в Национальной системе платежных карт (Мир Plat.form). И в этой статье я хочу рассказать, как эта скрытая сложность может догнать любого разработчика и что ему с этим делать.</p><h2>Личная история</h2><p>Опыт работы с многопоточной — часто это вопрос интереса, любознательности. Например, пишешь ты обработку запроса в сервлете. Вроде бы просто, скучно, понятно. Один запрос, один ответ, напиливаешь какую-то бизнес-логику или и того хуже, баги в этом правишь. И, казалось бы, причем тут конкурентная обработка? С одной стороны, это верно, не подкопаешься. А с другой это же сервер, тут десятки-сотни-тысячи одновременных запросов, сервак управляет жизненным циклом приложения, на низком уровне принимает запросы и отдает ответы в высоко конкурентном окружении. И это мы ещё не спускаемся на уровень JVM, ОС и ниже.</p><p>Java — достаточно выразительный язык разработки с огромным количеством фреймворков, которые скрывают всю сложность от разработчика. И возможна ситуация, когда, занимаясь разработкой лет десять, можно ни разу не столкнуться с многопоточным кодом, а он все равно есть, пусть и скрыт за реализацией внешних API. И это даже нормально, особенно в каком-нибудь enterprise.</p><p>Но дальше есть, скажем так, развилка. Можно спокойно пилить задачи, закрывать тикеты и быть молодцом. А можно копать и изучать, что же там, под капотом. Я давно слышу, что современные разработчики примерно в такой же ситуации, как и Алиса в зазеркалье. Чтобы просто оставаться на месте, надо бежать со всех ног. И ещё больше надо делать, чтобы продвигаться вперед. Согласны?</p><h2>Почему без этого плохо</h2><p>Но если все закрыто фреймворками, то зачем об этом вообще думать?</p><p>И тут вспоминается история, которая произошла недавно. И сразу спойлер — если бы разработчик держал в фокусе, что он работает в многопоточном окружении, то сэкономил бы несколько дней разработки.</p><p>Итак, у этого разработчика было время поработать над ускорением прохождения пакета регрессионных тестов. И для этого подключили несколько окружений и сменили систему журналирования логов на асинхронную.</p><p>И локально все работало отлично, а на стенде регресс начал разваливаться, причем делал это самым непредсказуемым образом. Разные сценарии, в разных местах, ещё и не регулярно.</p><p>Разработчик потратил один день, решил разобраться, углубиться. Прошел второй день, на третий — мы решили ему помочь. Оказалось, проблема именно в журналировании. Из-за изменения механизма на асинхронный, возникали гонки с бизнес-логикой, которая не предполагала асинхронное изменение сообщения во время обработки. В одном потоке могли происходить изменения состояния, пока объект был в очереди на запись в сокет.</p><p>И классно, что это был рефакторинг, а не хотфиксы перед выходом важного релиза. История, о которой я рассказал выше – не уникальна. Она может возникнуть в том или ином виде в любом проекте.</p><p>Таким образом, мы все равно работаем в многопоточном окружении, даже если этого не осознаем. Получается, даже в таком варианте можно выстрелить себе в ногу, если не думать о неочевидных побочных эффектах.</p><h2>А делать-то что?</h2><p>Допустим, надо знать многопоточку. Если повезло, и есть боевые задачи, то все хорошо. Но что делать, если их нет?</p><p>Лично мои рекомендации:</p><ul><li>прочитать JCIP, можно два раза;</li><li>придумать себе проект на неделю-две, например, написать собственный веб-сервер на голой Java (или на C/go/ваш любимый язык);</li><li>классные тестовые тоже подходят;</li><li>пойти под капот фреймворков из боевых проектов, смотреть как они устроены, профилировать их.</li></ul><p>Любой из этих пунктов уже даст больше опыта, чем обычно есть у людей, не занимающихся системной разработкой.</p><p>А как думаете вы, будет ли это полезным или у вас есть свой способ?</p>]]></content:encoded>
    </item>
    <item>
      <title>Зачем нужен Python Global Interpreter Lock и как он работает</title>
      <link>https://tproger.ru/translations/global-interpreter-lock-guide</link>
      <comments>https://tproger.ru/translations/global-interpreter-lock-guide?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/global-interpreter-lock-guide</guid>
      <description><![CDATA[<p>GIL позволяет управлять интерпретатором Python только одному потоку одновременно, что заметно сказывается на производительности многопоточных программ.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/global-interpreter-lock-guide">Зачем нужен Python Global Interpreter Lock и как он работает</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 08 Jun 2019 06:20:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Python Global Interpreter Lock (GIL) — это своеобразная блокировка, позволяющая только одному потоку управлять интерпретатором Python. Это означает, что в любой момент времени будет выполняться только один конкретный поток.</p><p>Работа GIL может казаться несущественной для разработчиков, создающих однопоточные программы. Но во многопоточных программах отсутствие GIL может негативно сказываться на производительности процессоро-зависымых программ.</p><p>Поскольку GIL позволяет работать только одному потоку даже в многопоточном приложении, он заработал репутацию «печально известной» функции.</p><p>В этой статье будет рассказано о том, как GIL влияет на производительность приложений, и о том, как это самое влияние можно смягчить.</p><h2>Что за проблему в Python решает GIL?</h2><p>Python подсчитывает количество ссылок для корректного управления памятью. Это означает, что созданные в Python объекты имеют переменную подсчёта ссылок, в которой хранится количество всех ссылок на этот объект. Как только эта переменная становится равной нулю, память, выделенная под этот объект, освобождается.</p><p>Вот небольшой пример кода, демонстрирующий работу переменных подсчёта ссылок:</p><p>В этом примере количество ссылок на пустой массив равно 3. На этот массив ссылаются: переменная a, переменная b и аргумент, переданный функции sys.getrefcount().</p><p>Проблема, которую решает GIL, связана с тем, что в многопоточном приложении сразу несколько потоков могут увеличивать или уменьшать значения этого счётчика ссылок. Это может привести к тому, что память очистится неправильно и удалится тот объект, на который ещё существует ссылка.</p><p>Счётчик ссылок можно защитить, добавив блокираторы на все структуры данных, которые распространяются по нескольким потокам. В таком случае счётчик будет изменяться исключительно последовательно.</p><p>Но добавление блокировки к нескольким объектам может привести к появлению другой проблемы — взаимоблокировки (англ. deadlocks), которая получается только если блокировка есть более чем на одном объекте. К тому же эта проблема тоже снижала бы производительность из-за многократной установки блокираторов.</p><p>GIL — эта одиночный блокиратор самого интерпретатора Python. Он добавляет правило: любое выполнение байткода в Python требует блокировки интерпретатора. В таком случае можно исключить взаимоблокировку, т. к. GIL будет единственной блокировкой в приложении. К тому же его влияние на производительность процессора совсем не критично. Однако стоит помнить, что GIL уверенно делает любую программу однопоточной.</p><p>Несмотря на то, что GIL используется и в других интерпретаторах, например в Ruby, он не является единственным решением этой проблемы. Некоторые языки решают проблему потокобезопасного освобождения памяти с помощью сборки мусора.</p><p>С другой стороны это означает, что такие языки часто должны компенсировать потерю однопоточных преимуществ GIL добавлением каких-то дополнительных функций повышения производительности, например JIT-компиляторов.</p><h2>Почему для решения проблемы был выбран именно GIL?</h2><p>Итак, почему же это не очень «хорошее» решение используется в Python? Насколько для разработчиков это решение критично?</p><p><a href="https://www.youtube.com/watch?v=KVKufdTphKs&amp;feature=youtu.be&amp;t=12m11s">По словам Larry Hastings</a>, архитектурное решение GIL — это одна из тех вещей, которые сделали Python популярным.</p><p>Python существует с тех времён, когда в операционных системах не существовало понятия о потоках. Этот язык разрабатывался в расчёте на лёгкое использование и ускорение процесса разработки. Всё больше и больше разработчиков переходило на Python.</p><p>Много расширений, в которых нуждался Python, было написано для уже существующих библиотек на C. Для предотвращения несогласованных изменений, язык C требовал потокобезопасного управления памятью, которое смог предоставить GIL.</p><p>GIL можно было легко реализовать и интегрировать в Python. Он увеличивал производительность однопоточных приложений, поскольку управление велось только одним блокиратором.</p><p>Те библиотеки на C, которые не были потокобезопасными, стало легче интегрировать. Эти расширения на C стали одной из причин, почему Python-сообщество стало расширяться.</p><p>Как можно понять, GIL — фактическое решение проблемы, с которой столкнулись разработчики CPython в начале жизни Python.</p><h2>Влияние GIL на многопоточные приложения</h2><p>Если смотреть на типичную программу (не обязательно написанную на Python) — есть разница, ограничена ли эта программа производительностью процессора или же I/O.</p><p>Операции, ограниченные производительностью процессора (англ. CPU-bound) — это все вычислительные операции: перемножение матриц, поиск, обработка изображений и т. д.</p><p>Операции, ограниченные производительностью I/O (англ. I/O-bound) — это те операции, которые часто находятся в ожидании чего-либо от источников ввода/вывода (пользователь, файл, БД, сеть). Такие программы и операции иногда могут ждать долгое время, пока не получат от источника то, что им нужно. Это связано с тем, что источник может проводить собственные (внутренние) операции, прежде чем он будет готов выдать результат. Например, пользователь может думать над тем, что именно ввести в поисковую строку или же какой запрос отправить в БД.</p><p>Ниже приведена простая CPU-bound программа, которая попросту ведёт обратный отсчёт:</p><p>Запустив это на 4х-ядерном компьютере получим такой результат:</p><p>Ниже приведена та же программа, с небольшим изменением. Теперь обратный отсчёт ведётся в двух параллельных потоках:</p><p>И вот результат:</p><p>Как видно из результатов, оба варианта затратили примерно одинаковое время. В многопоточной версии GIL предотвратил параллельное выполнение потоков.</p><p>GIL не сильно влияет на производительность I/O-операций в многопоточных программах, т. к. в процессе ожидания от I/O блокировка распространяется по потокам.</p><p>Однако программа, потоки которой будут работать исключительно с процессором (например обработка изображения по частям), из-за блокировки не только станет однопоточной, но и на её выполнение будет затрачиваться больше времени, чем если бы она изначально была строго однопоточной.</p><p>Такое увеличение времени — это результат появления и реализации блокировки.</p><h2>Почему GIL всё ещё используют?</h2><p>Разработчики языка получили уйму жалоб касательно GIL. Но такой популярный язык как Python не может провести такое радикальное изменение, как удаление GIL, ведь это, естественно, повлечёт за собой кучу проблем несовместимости.</p><p>В прошлом разработчиками были предприняты попытки удаления GIL. Но все эти попытки разрушались существующими расширениями на C, которые плотно зависели от существующих GIL-решений. Естественно, есть и другие варианты, схожие с GIL. Однако они либо снижают производительность однопоточных и многопоточных I/O-приложений, либо попросту сложны в реализации. Вам бы не хотелось, чтобы в новых версиях ваша программа работала медленней, чем сейчас, ведь так?</p><p>Создатель Python, Guido van Rossum, в сентябре 2007 года высказался по поводу этого в статье «<a href="https://www.artima.com/weblogs/viewpost.jsp?thread=214235">It isn’t Easy to remove the GIL</a>»:</p><blockquote>«Я был бы рад патчам в Py3k только в том случае, если бы производительность однопоточных приложений или многопоточных I/O-приложений не уменьшалась.»</blockquote><p>С тех пор ни одна из предпринятых попыток не удовлетворяла это условие.</p><h2>Почему GIL не был удалён в Python 3?</h2><p>Python 3 на самом деле имел возможность переделки некоторых функций с нуля, хотя из-за этого многие расширения на С попросту сломались бы и их пришлось бы переделывать. Именно из-за этого первые версии Python 3 так слабо расходились по сообществу.</p><p>Но почему бы параллельно с обновлением Python 3 не удалить GIL?</p><p>Его удаление сделает однопоточность в Python 3 медленней по сравнению с Python 2 и просто представьте, во что это выльется. Нельзя не заметить преимущества однопоточности в GIL. Именно поэтому он всё ещё не удалён.</p><p>Но в Python 3 действительно появились улучшения для существующего GIL. До этого момента в статье рассказывалось о влиянии GIL на многопоточные программы, которые затрагивают только процессор или только I/O. А что насчёт тех программ, у которых часть потоков идут на процессор, а часть на I/O?</p><p>В таких программах I/O-потоки «страдают» из-за того, что у них нет доступа к GIL от процессорных потоков. Это связано со встроенным в Python механизмом, который принуждал потоки освобождать GIL после определённого интервала непрерывного использования. В случае, если никто другой не используют GIL, эти потоки могли продолжать работу.</p><p>Но тут есть одна проблема. Почти всегда GIL занимается процессорными потоками и остальные потоки не успевают занять место. Этот факт был изучен David Beazley, визуализацию этого можно увидеть <a href="http://www.dabeaz.com/blog/2010/01/python-gil-visualized.html">здесь</a>.</p><p>Проблема была решена в Python 3.2 в 2009 разработчиком Antoine Pitrou. Он добавил механизм подсчёта потоков, которые нуждаются в GIL. И если есть другие потоки, нуждающиеся в GIL, текущий поток не занимал бы их место.</p><h2>Как справиться GIL?</h2><p>Если GIL у вас вызывает проблемы, вот несколько решений, которые вы можете попробовать:</p><p>Многопроцессность против многопоточности. Довольно популярное решение, поскольку у каждого Python-процесса есть собственный интерпретатор с выделенной под него памятью, поэтому с GIL проблем не будет. В Python уже есть модуль multiprocessing, который упрощает создание процессов к такому виду:</p><p>После запуска получаем такой результат:</p><p>Можно заметить приличное повышение производительности по сравнению с многопоточной версией. Однако показатель времени не снизился до половины. Всё из-за того, что управление процессами само по себе сказывается на производительности. Несколько процессов более сложны, чем несколько потоков, поэтому с ними нужно работать аккуратно.</p><p>Альтернативные интерпретаторы Python. У Python есть много разных реализаций интерпретаторов. CPython, Jyton, IronPython и PyPy, написанные на C, Java, C# и Python соответственно. GIL существует только на оригинальном интерпретаторе — на CPython.</p><p>Вы просто можете использовать преимущества однопоточности, в то время, пока одни из самых ярких умов прямо сейчас работают над устранением GIL из CPython. <a href="https://github.com/larryhastings/gilectomy">Вот одна из попыток</a>.</p><p>Зачастую, GIL рассматривается как нечто-то сложное и непонятное. Но имейте ввиду, что как python-разработчик, вы столкнётесь с GIL только если будете писать расширения на C или многопоточные процессорные программы.</p><p>На этом этапе вы должны понимать все аспекты, необходимые при работе с GIL. Если же вам интересна низкоуровневая структура GIL — посмотрите  <a href="https://youtu.be/Obt-vMVdM8s">Understanding the Python GIL</a> от David Beazley.</p>]]></content:encoded>
    </item>
    <item>
      <title>Многопоточность в Node.js</title>
      <link>https://tproger.ru/translations/guide-to-threads-in-node-js</link>
      <comments>https://tproger.ru/translations/guide-to-threads-in-node-js?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Klara Oswald]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/guide-to-threads-in-node-js</guid>
      <description><![CDATA[<p>Node.js использует цикл обработки событий и пул воркеров: первый регистрирует callback-функции, второй вызывает и обрабатывает отдельные потоки.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/guide-to-threads-in-node-js">Многопоточность в Node.js</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Node.js]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 13 Apr 2019 10:00:33 GMT</pubDate>
      <content:encoded><![CDATA[<p>Некоторые разработчики удивляются, как однопоточный <a href="https://nodejs.org/en/">Node.js</a> может конкурировать с многопоточным серверным софтом. Кажется нелогичным, что компании выбирают его в качестве backend. Для начала надо разобраться в том, что на самом деле подразумевается под однопоточностью Node.</p><p>JavaScript был создан для реализации простых web-задач вроде проверки формы или создания следа у курсора. Только в 2009 году Райан Дал (создатель Node.js) <a href="https://www.youtube.com/watch?v=ztspvPYybIY">сделал возможным</a> использование этого языка для написания backend-софта.</p><p>Backend-языки, поддерживающие многопоточность, имеют необходимые механизмы для синхронизации значений между потоками и другими поточно-ориентированными функциями. Для поддержки этого в JavaScript потребовалось бы изменить весь язык, что не входило в планы Дала. Пришлось создать обходной путь, чтобы простой JavaScript мог поддерживать многопоточность.</p><h2>Как на самом деле работает Node.js</h2><p>Node.js использует два вида потоков:</p><ul><li>основной поток, обрабатываемый циклом событий (Event Loop),</li><li>несколько вспомогательных потоков в пуле воркеров.</li></ul><p>Цикл обработки событий — это механизм, который принимает callback-функции и регистрирует их для выполнения в определённый момент в будущем. Он работает в том же потоке, что и сам код JavaScript. Когда операция блокирует поток, цикл событий также блокируется.</p><p>Пул воркеров — модель исполнения, вызывающая и обрабатывающая отдельные потоки. Затем они синхронно выполняют задачу и возвращают результат в цикл обработки событий. После цикл вызывает callback-функцию с указанным результатом.</p><p>Если коротко, то пул воркеров может заниматься асинхронными операциями ввода-вывода — прежде всего, взаимодействем с системным диском и сетью. Эта модель исполнения в основном используется модулями вроде fs (требовательного к скорости ввода-вывода) или crypto (требовательного к CPU). Пул воркеров реализован в <a href="http://docs.libuv.org/en/v1.x/">libuv</a>, что приводит к небольшой задержке всякий раз, когда Node требует связи между JavaScript и C ++, но эта задержка едва ощутима.</p><p>Используя оба эти механизма, можно написать следующий код:</p><p>Модуль fs указывает пулу воркеров использовать один из его потоков для чтения содержимого файла и уведомления цикла обработки событий, когда это будет сделано. Цикл принимает предоставленную callback-функцию и выполняет её с содержимым файла.</p><p>Выше приведён пример неблокирующего кода. Пул воркеров прочитает файл и вызовет предоставленную функцию с результатом. Поскольку пул имеет собственные потоки, цикл обработки событий может продолжать исполнение в обычном режиме во время чтения файла.</p><p>Всё работает, пока нет необходимости синхронно выполнять какую-то сложную операцию. Любая функция, выполнение которой занимает слишком много времени, блокирует поток. Если в приложении много таких функций, оно может значительно снизить производительность сервера или вообще заморозить его работу в целом. В этом случае нет способа делегировать работу пулу воркеров.</p><p>Области, требующие сложных вычислений, — искусственный интеллект, машинное обучение или большие данные— не могли эффективно использовать Node.js из-за операций, блокирующих основной (и единственный) поток, что делало сервер неотзывчивым. Так было до появления Node.js v10.5.0, в котором была добавлена поддержка нескольких потоков.</p><h2>Знакомство с worker_threads</h2><p>Модуль worker_threads — это пакет, который позволяет создавать полнофункциональные многопоточные приложения Node.js.</p><p>Потоковый воркер (thread worker) — фрагмент кода (обычно извлекаемый из файла), созданный в отдельном потоке.</p><p>Для использования потоковых воркеров нужно импортировать модуль worker_threads. Начнём с создания функции, которая поможет создавать эти воркеры, а также рассмотрим их свойства.</p><p>Для создания потокового воркера необходимо создать экземпляр класса Worker. В первом аргументе указываем путь к файлу, который содержит код воркера; во втором предоставляем объект, содержащий свойство с именем workerData. Это те данные, к которым поток будет иметь доступ при запуске, если того хочет разработчик.</p><p>Обратите внимание: независимо от того, используете ли вы сам JavaScript или что-то, что в него транспилируется (например TypeScript), путь всегда должен ссылаться на файлы с расширениями .js или .mjs.</p><p>Также стоит указать, почему используется callback-функция вместо возвращения промиса (promise), который будет передавать результат в resolve при запуске события message. Это связано с возможностью потоковых воркеров отправлять много событий message, а не только одно.</p><p>Связь между потоками основана на событиях. Это означает, что надо настроить обработчики, которые будут вызываться после отправки потоком данного события.</p><p>Рассмотрим наиболее распространённые события.</p><p>Событие error генерируется, когда внутри воркера возникает необработанное исключение. Затем поток завершается, а ошибка становится первым аргументом в callback.</p><p>Exit генерируется, когда воркер заканчивает своё выполнение. Если process.exit() вызывается внутри потока, exitCode будет предоставлен в callback. Если поток прерывается с помощью worker.terminate(), код будет 1.</p><p>Online генерируется, когда воркер прекращает парсинг кода JavaScript и начинает его выполнение. Это событие используется нечасто, но в определённых случаях оно может быть информативным.</p><p>Message генерируется, когда воркер отправляет данные в родительский поток.</p><h2>Обмен данными между потоками</h2><p>Для отправки данных другому потоку используется метод port.postMessage(). Он имеет следующую сигнатуру:</p><p>Объект port может быть или экземпляром parentPort, или экземпляром MessagePort — подробнее об этом позже.</p><h3>Аргумент data</h3><p>Первый аргумент данных — назовём его data — это объект, который копируется в другой поток. Он может содержать всё, что поддерживает алгоритм копирования.</p><p>Данные копируются <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm">алгоритмом структурированного клонирования</a>.</p><p>Алгоритм не копирует функции, ошибки, дескрипторы свойств или цепочки прототипов. Следует также отметить, что копирование объектов таким способом отличается от JSON, потому что он может содержать циклические ссылки и типизированные массивы, а JSON не может.</p><p>Поддерживая копирование типизированных массивов, алгоритм позволяет разделять память между потоками.</p><h3>Разделение памяти между потоками</h3><p>Считается, что модули вроде cluster или child_process используют потоки уже давно. Это одновременно и верно и нет.</p><p>Cluster может создавать несколько процессов Node.js с одним главным процессом, маршрутизирующим запросы между ними. Кластеризация приложения позволяет эффективно увеличить пропускную способность сервера. Однако нельзя создать отдельный поток с модулем cluster.</p><p>Модуль child_process может создавать любой исполняемый файл независимо от типа файла. В этом модуле отсутствуют некоторые важные функции, которые есть у worker_threads.</p><p>Потоковые воркеры являются более лёгкими и имеют тот же идентификатор процесса, что и их родительские потоки. Ещё они могут использовать память совместно со своими родительскими потоками. Это позволяет воркерам избежать сериализации больших входных данных и, как следствие, отправлять данные вперёд и назад более эффективно.</p><p>Рассмотрим пример разделения памяти между потоками. Чтобы память была разделена, экземпляры ArrayBuffer или SharedArrayBuffer должны быть отправлены другому потоку в качестве аргумента data или внутри него.</p><p>Пример воркера, который разделяет память со своим родительским потоком:</p><p>Создаётся экземпляр SharedArrayBuffer с размером памяти, необходимым для хранения ста 32-битных целых чисел. Затем создаётся экземпляр Int32Array, который будет использовать буфер для хранения его структуры. После массив заполняется некоторыми случайными числами и отправляется в родительский поток.</p><p>В родительском потоке:</p><p>Меняя значение arr[0] на 5, фактически изменяем его в обоих потоках.</p><p>При разделении памяти есть риск изменить значение в одном потоке, изменив его в другом. Но вместе с этим появляется хорошая особенность: значение не нужно сериализовывать, чтобы оно было доступно в другом потоке. Это значительно повышает эффективность. Просто не забывайте правильно управлять ссылками на данные, чтобы те в свою очередь не оставляли за собой мусор после завершения работы с ними.</p><p>Зачастую гораздо удобнее передавать между потоками не массив, а объект. Но, к сожалению, не существует SharedObjectBuffer или чего-либо подобного, но можно самим <a href="https://stackoverflow.com/questions/51053222/nodejs-worker-threads-shared-object-store">создать похожую структуру</a>.</p><h3>Аргумент TransferList</h3><p>TransferList может содержать только ArrayBuffer и MessagePort. После передачи в другой поток их больше нельзя использовать в отправляющем потоке. Память перемещается в другой поток и, следовательно, недоступна в отправляющем.</p><p>Пока нет канала связи, нельзя передавать сетевые сокеты, включая их в TransferList.</p><h3>Создание канала связи</h3><p>Связь между потоками осуществляется через порты, которые являются экземплярами класса MessagePort. Они обеспечивают эту связь на основе событий.</p><p>Есть два способа использования портов для связи между потоками. Первый используется по умолчанию и проще, чем второй. В код воркера импортируется объект с именем parentPort из модуля worker_threads и используется .postMessage() для отправки сообщений в родительский поток.</p><p>Пример:</p><p>parentPort — это экземпляр MessagePort, который Node.js создал “за кулисами”, чтобы обеспечить связь с родительским потоком. Таким образом, можно общаться между потоками, используя объекты parentPort и worker.</p><p>Второй способ связи между потоками — создать MessageChannel и отправить его воркеру. Вот как можно создать новый MessagePort и поделиться им с потоковым воркером:</p><p>После создания port1 и port2 настраиваем обработчики событий на port1 и отправляем port2 воркеру. Необходимо включить его в файл TransferList, чтобы он был передан рабочей стороне.</p><p>Теперь внутри воркера:</p><p>Таким образом, используется порт, который был отправлен родительским потоком.</p><p>Использование parentPort тоже является правильным подходом, но лучше создать новый MessagePort с экземпляром MessageChannel, а затем поделиться им с созданным воркером.</p><p>Обратите внимание, в примерах ниже для простоты используется parentPort.</p><h3>Два способа использования воркеров</h3><p>Первый — создать воркер, выполнить его код и отправить результат в родительский поток. При таком подходе каждый раз, когда появляется новая задача, надо заново создавать воркер.</p><p>Второй способ — создать воркер и настроить обработчики для события message. Каждый раз при запуске это событие выполняет свою работу и отправляет результат обратно в родительский поток, который сохраняет воркер для последующего использования.</p><p>Документация Node.js рекомендует второй подход, поскольку много усилий необходимо для создания потокового воркера, который требует создания виртуальной машины, парсинга и выполнения кода. Этот метод также намного эффективнее, чем постоянно создающиеся воркеры.</p><p>Такой подход называется пулом воркеров. Создаётся пул и воркеры находятся в ожидании события message, которое нужно для выполнения задания.</p><p>Пример файла, содержащего воркер, который создаётся, выполняется, а затем закрывается:</p><p>После отправки collection в родительский поток, воркер просто завершается.</p><p>А вот пример воркера, который может ждать в течение длительного периода времени, прежде чем ему будет дано задание:</p><h2>Полезные свойства модуля worker_threads</h2><p>isMainThread</p><p>Свойство имеет значение true, когда не работает внутри потока воркера. Если есть необходимость, можно включить простой оператор if в начале файла, чтобы убедиться, что он запускается только как воркер.</p><p>workerData</p><p>Несёт в себе данные, включённые в конструктор воркера созданным потоком.</p><p>В потоке воркера:</p><p>parentPort</p><p>Экземпляр MessagePort, используется для связи с родительским потоком.</p><p>threadId</p><p>Уникальный идентификатор, присвоенный воркеру.</p><h2>Реализация setTimeout</h2><p>setTimeout — это бесконечный цикл, который прерывает выполнение приложения. На практике он проверяет на каждой итерации, меньше ли сумма начальной даты и заданного количества миллисекунд, чем фактическая дата.</p><p>Эта конкретная реализация создаёт поток, выполняет его код и затем завершает работу.</p><p>Реализуем код, который будет использовать этот воркер. Создадим стейт, в котором будут отслеживаться созданные воркеры:</p><p>Функция, которая отвечает за создание потоковых воркеров и хранит их в стейт:</p><p>Используем пакет UUID для создания уникального идентификатора воркера, затем задействуем определённую ранее вспомогательную функцию runWorker(), чтобы получить воркер. Передаём ему callback-функцию, которая запускается после отправки воркером некоторых данных. Сохраняем воркер в стейт и возвращаем id.</p><p>Внутри callback-функции нужно проверить, существует ли воркер в стейте, потому что есть возможность отменить его с помощью cancelTimeout(). Если он существует, удаляем его из стейта и вызываем callback, переданный в функцию setTimeout().</p><p>Функция cancelTimeout() использует метод .terminate(), чтобы принудительно остановить воркер и удалить его из стейта:</p><p>Прим. если вам интересно, есть <a href="https://github.com/maciejcieslar/threads-nodejs/blob/master/src/timeout/timeout.ts#L64">реализация</a> метода setInterval(). Но он не имеет ничего общего с потоками (повторно используется код setTimeout()). Кроме того, существует небольшой тестовый код для проверки, насколько такой подход отличается от исходного. Вы можете просмотреть код <a href="https://github.com/maciejcieslar/threads-nodejs/blob/master/src/timeout/index.ts#L13">здесь</a>. Результаты:</p><p>Видно, что в setTimeout() есть небольшая задержка — около 40 мс — из-за создаваемого воркера. Средняя стоимость процессора также немного выше, но ничего страшного в этом нет (стоимость процессора — это среднее значение загрузки процессора за всё время процесса).</p><p>Если бы можно было повторно использовать воркеры, задержка и загрузка ЦП снизилась бы. Поэтому рассмотрим, как реализовать собственный пул воркеров.</p><h2>Реализация пула воркеров</h2><p>Пул воркеров — это заданное количество ранее созданных воркеров, которые ожидают событие message. Как только событие происходит, воркеры выполняют работу и отправляют результат обратно.</p><p>Вот как можно создать пул воркеров из восьми рабочих потоков:</p><p>Если вы знакомы с <a href="https://medium.freecodecamp.org/how-to-limit-concurrent-operations-in-javascript-b57d7b80d573">ограничением параллельных операций</a>, то знаете, что логика здесь почти одинакова.</p><p>Из фрагмента выше видно, конструктору WorkerPool передаётся количество воркеров и путь для их появления.</p><p>Здесь есть дополнительные свойства вроде workerById и activeWorkersById, в которых можно сохранить существующие воркеры и их идентификаторы соответственно. Также есть queue (очередь), в которой можно сохранять объекты со следующей структурой:</p><p>callback — callback-функция в Node по умолчанию с ошибкой в качестве первого аргумента и возможным результатом в качестве второго. getData — это функция, передаваемая методу .run() пула воркеров (поясняется ниже), которая вызывается после начала обработки элемента. Данные, возвращаемые функцией getData(), будут переданы в рабочий поток.</p><p>Внутри метода .init() создаём воркеры и сохраняем их в стейтах:</p><p>Для избежания бесконечных циклов нужно убедиться, что количество потоков больше 1. Создаём необходимое число воркеров и сохраняем их по индексу в стейте workerById. Также сохраняем информацию, работают ли они в настоящее время, в стейте activeWorkersById, который всегда по умолчанию имеет значение false.</p><p>Реализуем метод .run() для настройки задачи, которая будет запущена, как только воркер станет доступен.</p><p>Внутри функции, переданной в промис, проверяем, есть ли доступный для обработки данных воркер, вызывая .getInactiveWorkerId():</p><p>Создаём queueItem, в котором сохраняем переданную методу .run() функцию getData() в качестве callback. В этом callback разрешаем (resolve) или отклоняем (reject) промис в зависимости от того, передал ли воркер callback.</p><p>Если значение availableWorkerId равно -1, доступного воркера нет. В этом случае добавляем queueItem в queue. Если есть доступный воркер, вызываем метод .runWorker() для его выполнения.<br />В методе .runWorker() в стейте activeWorkersById необходимо установить, что воркер в данный момент используется. Также нужно настроить обработчики для событий message и error (после очистить их). И, наконец, отправить данные воркеру.</p><p>Используя переданный workerId, получаем ссылку на воркер из стейта workerById. Внутри activeWorkersById устанавливаем в свойстве [workerId] значение true. Таким образом будет известно, что больше ничего не нужно запускать, пока воркер занят.</p><p>Создаём messageCallback() и errorCallback() для вызова событий message и error соответственно. Регистрируем указанные функции для обработки события и отправки данных воркеру.</p><p>Внутри функций вызываем callback в queueItem, а затем вызываем функцию cleanUp(). Убеждаемся, что обработчики событий удаляются, т. к. один и тот же воркер используется многократно. Если не удалить обработчики, произойдёт утечка памяти (память медленно исчерпается).</p><p>В стейте activeWorkersById устанавливаем для свойства [workerId] значение false и проверяем, пуста ли очередь. Если это не так, удаляем первый элемент из queue и снова вызываем воркер с другим queueItem.</p><p>Создадим воркер, который выполняет некоторые вычисления после получения данных в событии message:</p><p>Потоковый воркер создаёт массив из 1 миллиона случайных чисел, а затем сортирует их.</p><p>Пример простого использования пула воркеров:</p><p>Всё начиналось с создания пула из восьми воркеров. Затем был создан массив из 100 элементов и для каждого элемента запускалась задача в пуле воркеров. Первые восемь задач были выполнены немедленно, а остальные помещены в очередь и выполнены постепенно. Благодаря использованию пула воркеров не нужно каждый раз создавать воркер, что значительно повышает эффективность.</p><h2>Заключение</h2><p>worker_threads предоставляет простой способ добавить поддержку многопоточности в приложения. Передавая тяжёлые CPU-вычисления другим потокам, можно значительно увеличить пропускную способность сервера. Благодаря официальной поддержке потоков можно ожидать, что всё больше разработчиков и инженеров из различных областей (ИИ, машинное обучение и большие данные) начнут использовать Node.js.</p>]]></content:encoded>
    </item>
    <item>
      <title>Курс «Теория и практика многопоточного программирования»</title>
      <link>https://tproger.ru/video/multithreaded-programming-2</link>
      <comments>https://tproger.ru/video/multithreaded-programming-2?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дмитрий Баранчук]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/multithreaded-programming-2</guid>
      <description><![CDATA[<p>Русскоязычный курс охватывает параллельные программы, доказательство корректности алгоритмов, согласованность многопоточных программ и их ошибки.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/multithreaded-programming-2">Курс «Теория и практика многопоточного программирования»</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 14 Jul 2017 11:17:50 GMT</pubDate>
      <content:encoded><![CDATA[<p>Русскоязычный курс, рассказывающий о теоретических основах написания параллельных программ, математических подходах к доказательству корректности параллельных алгоритмов, разработке неожидающих параллельных алгоритмов, ошибках в параллельных программах и способах их решения.</p><p>Помимо этого в курсе рассматривается архитектура многоядерных систем с разделяемой памятью. Вводится математическая модель параллельного исполнения, рассматривается способ построения рассуждения в терминах модели. Вводятся понятия согласованности многопоточной программы, и доказывается ряд теорем, позволяющих предсказывать поведения алгоритмов, построенных на базе известных примитивов.</p>]]></content:encoded>
    </item>
    <item>
      <title>Курс «Многопоточный C++»</title>
      <link>https://tproger.ru/video/cpp-multithreading</link>
      <comments>https://tproger.ru/video/cpp-multithreading?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дмитрий Баранчук]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/video/cpp-multithreading</guid>
      <description><![CDATA[<p>Русскоязычный видеокурс «Техносфера Mail.ru Group» обучает многопоточному программированию на C++ и дополнительно рассматривает сеть и контейнеры STL и boost.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/video/cpp-multithreading">Курс «Многопоточный C++»</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Обучающие курсы]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Видео]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 10 Jun 2017 15:43:18 GMT</pubDate>
      <content:encoded><![CDATA[<p>Русскоязычный видеокурс, посвященный изучению основ многопоточного программирования на языке C++. Курс записан в 2015 году в рамках проекта «Техносфера Mail.ru Group». Лектор курса — Дмитрий Калугин-Балашов.</p><p>В рамках курса рассматриваются следующие темы:</p><ul><li>классическое создание дочерних процессов (через fork);</li><li>использование средств межпроцессного взаимодействия (IPC);</li><li>способы создания многопоточного приложения (pthreads, std::thread, boost::thread);</li><li>более высокоуровневые средства распараллеливания (OpenMP, Intel TBB).</li></ul><p>В курсе дополнительно представлены способы работы с сетью и контейнеры (STL, boost).</p><p>Смотрите также другие наши материалы по многопоточному программированию.</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные принципы программирования: конкурентность</title>
      <link>https://tproger.ru/translations/programming-concepts-concurrency</link>
      <comments>https://tproger.ru/translations/programming-concepts-concurrency?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/programming-concepts-concurrency</guid>
      <description><![CDATA[<p>Конкурентность — свойство систем, допускающее одновременное выполнение нескольких взаимодействующих процессов в пересекающихся промежутках.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/programming-concepts-concurrency">Основные принципы программирования: конкурентность</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 07 Jan 2017 14:56:31 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Аарон Краус</p><p>В третьей статье <a href="https://tproger.ru/tag/programming-concepts/">цикла “Принципы программирования”</a> мы поговорим о конкурентности (concurrency). <a href="https://en.wikipedia.org/wiki/Concurrency_(computer_science)">Конкурентность</a> — это свойство систем (программы, сети, компьютера и т.д.), допускающее одновременное выполнение нескольких вычислительных процессов, которые могут взаимодействовать друг с другом. Вычисления запускаются, проходят и завершаются в пересекающихся промежутках времени; они также могут происходить абсолютно одновременно (<a href="https://en.wikipedia.org/wiki/Parallel_computing">параллелизм</a>), но это не обязательно.</p><h3>Конкурентность в программировании</h3><p>Конкурентность реализована в логике программирования таким образом, что она явно устанавливает отдельные точки исполнения вычислений или процессов, называемые управляющими потоками. Они позволяют этим вычислениям избежать ожидания завершения всех остальных вычислений — как это происходит в случае последовательного программирования.</p><h3>Конкурентные и параллельные вычисления</h3><p>Хотя и считается, что конкурентные вычисления включают в себя параллельные, у них есть существенные отличия.</p><p>Параллельные вычисления используют более одного вычислительного ядра, поскольку все управляющие потоки работают одновременно и занимают весь рабочий цикл ядра на время исполнения — именно поэтому параллельное вычисление невозможно на одноядерном компьютере. В этом они отличаются от конкурентных вычислений, которые фокусируются на пересечениях жизненных циклов вычислений. Например, этапы выполнения процесса могут быть разбиты на <a href="https://ru.wikipedia.org/wiki/Разделение_времени">временные промежутки</a>, и если процесс не заканчивает своё существование до конца промежутка, он приостанавливается, предоставляя другому процессу возможность работать.</p><h3>Зачем нужно конкурентное программирование?</h3><p>Главным преимуществом этого подхода является максимально возможное использование ресурсов системы. В начале 2000-ых стало популярным использование многоядерных процессоров, пришедших на смену одноядерным, пускай и с очень мощным на то время ядром. Это в первую очередь позволяет оптимизировать время выполнения программы путём разделения нагрузки на ядра.  Я не буду углубляться в эту тему — принципы синхронизации процессов и потоков различны в разных ОС — но вы можете почитать об этом, например, <a href="https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29">здесь</a>.</p><p>В современных языках программирования принцип конкурентности обычно реализован в виде процесса <a href="https://en.wikipedia.org/wiki/Thread_(computing)#Multithreading">многопоточности</a>. Многопоточность позволяет программе работать в нескольких потоках, предоставляя преимущества параллелизации (быстрое исполнение, эффективное использование ресурсов и т.д.), но она также сохраняет и её недостатки (о них ниже), поэтому некоторые языки используют механизм, названный <a href="https://ru.wikipedia.org/wiki/Global_Interpreter_Lock">Global Interpreter Lock</a> (GIL). Обычно его можно встретить в стандартных реализациях Python и Ruby (CPython и Ruby MRI соответственно), он предотвращает одновременное исполнение более чем одного потока — даже на многоядерных процессорах. Это похоже на недостаток, но GIL нужен для предотвращения <a href="https://en.wikipedia.org/wiki/Thread_safety">потоконебезопасных</a> активностей. Реализации с GIL обычно повышают скорость работы однопоточных программ и облегчают интеграцию с библиотеками C, но за это приходится платить возможностями многопоточности.</p><h3>Проблемы конкурентного программирования</h3><p>Поскольку идеей конкурентности является одновременное исполнение вычислений, может случиться так, что эти отдельные вычисления получат доступ к разделёнными между ними ресурсам и случайно изменят их (так называемое потоконебезопасное поведение). В таких случаях используются <a href="https://en.wikipedia.org/wiki/Arbiter_(electronics)">арбитры</a>, но такой тип активности может создать неопределённость и привести к <a href="https://ru.wikipedia.org/wiki/Взаимная_блокировка">взаимной блокировке</a> и <a href="https://en.wikipedia.org/wiki/Starvation_(computer_science)">голоданию</a>.</p><p>Всё это делает координацию конкурентных задач очень важной, поскольку даже в тех местах, над которыми у разработчика почти нет власти — например, в распределении памяти <a href="https://tproger.ru/translations/programming-concepts-1/">в стеке и куче</a> — может возникнуть неопределённость.</p><p>Прим. перев. Рекомендуем также почитать наши материалы по теме многопоточного программирования:</p><ul><li><a href="https://tproger.ru/problems/safe-threads-in-cpp/">Безопасность потоков в С++</a>;</li><li><a href="https://tproger.ru/translations/multithreaded-exception-handling/">Обработка исключений в многопоточных приложениях</a>;</li><li><a href="https://tproger.ru/problems/write-a-class-that-provides-a-lock-so-as-to-prevent-the-occurrence-of-dead-lock/">Задача с разбором решения: “Разработайте класс, обеспечивающий блокировку так, чтобы предотвратить возникновение мертвой блокировки”</a>.</li></ul><h3>Будущее конкурентного программирования</h3><p>Конкурентное программирование — это очень мощный инструмент, даже с учётом его недостатков. Несколько языков предоставляют феноменальную поддержку инструментов для использования этого принципа: больше всего это проявляется в API языка <a href="https://golang.org/">Go</a>, созданного в Google. Разработчики других языков, например, Python и Ruby, видят отрицательные стороны конкурентности и поэтому используют GIL по умолчанию. Так или иначе, если вы пишете приложение, которое использует принципы конкурентности, обязательно уделите особое внимание планированию, иначе вы рискуете повредить данные.</p>]]></content:encoded>
    </item>
    <item>
      <title>Многопоточное программирование в Java 8. Часть третья. Атомарные переменные и конкурентные таблицы</title>
      <link>https://tproger.ru/translations/java8-concurrency-tutorial-3</link>
      <comments>https://tproger.ru/translations/java8-concurrency-tutorial-3?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/java8-concurrency-tutorial-3</guid>
      <description><![CDATA[<p>Заключительная часть руководства по параллельному программированию в Java 8: классы пакета java.concurrent.atomic и конкурентные таблицы.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/java8-concurrency-tutorial-3">Многопоточное программирование в Java 8. Часть третья. Атомарные переменные и конкурентные таблицы</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 25 Sep 2016 22:24:41 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает <a href="http://winterbe.com">Бенджамин Винтерберг</a>, Software Engineer</p><p>Добро пожаловать в третью часть руководства по параллельному программированию в Java 8. В <a href="https://tproger.ru/translations/java8-concurrency-tutorial-1/">первой части</a> мы рассматривали, как выполнять код параллельно с помощью потоков, задач и сервисов исполнителей. Во <a href="https://tproger.ru/translations/java8-concurrency-tutorial-2">второй</a> разбирались с тем, как синхронизировать доступ к изменяемым объектам с помощью ключевого слова synchronized, блокировок и семафоров. Сегодня, в заключительной части, я расскажу о двух очень важных частях Concurrency API: об атомарных переменных и о конкурентных таблицах (Concurrent Maps).</p><h3>AtomicInteger</h3><p>Пакет java.concurrent.atomic содержит много полезных классов для выполнения атомарных операций. Операция называется атомарной тогда, когда её можно безопасно выполнять при параллельных вычислениях в нескольких потоках, не используя при этом ни блокировок, ни synchronized, как мы это делали в предыдущем уроке.</p><p>Внутри атомарные классы очень активно используют <a href="https://ru.wikipedia.org/wiki/Сравнение_с_обменом">сравнение с обменом</a> (compare-and-swap, CAS), атомарную инструкцию, которую поддерживает большинство современных процессоров. Эти инструкции работают гораздо быстрее, чем синхронизация с помощью блокировок. Поэтому, если вам просто нужно изменять одну переменную с помощью нескольких потоков, лучше выбирать атомарные классы.</p><p>Приведу несколько примеров с использованием AtomicInteger, одного из атомарных классов:</p><p>Как видите, использование AtomicInteger вместо обычного Integer позволило нам корректно увеличить число, распределив работу сразу по двум потокам. Мы можем не беспокоиться о безопасности, потому что incrementAndGet() является атомарной операцией.</p><p>Класс AtomicInteger поддерживает много разных атомарных операций. Метод updateAndGet() принимает в качестве аргумента лямбда-выражение и выполняет над числом заданные арифметические операции:</p><p>Метод accumulateAndGet() принимает лямбда-выражения типа IntBinaryOperator. Вот как мы можем использовать его, чтобы просуммировать все числа от нуля до тысячи:</p><p>Среди других атомарных классов хочется упомянуть такие как <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicBoolean.html">AtomicBoolean</a>, <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicLong.html">AtomicLong</a> и <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html">AtomicReference</a>.</p><h3>LongAdder</h3><p>Класс LongAdder может выступать в качестве альтернативы AtomicLong для последовательного сложения чисел.</p><p>Так же, как и у других атомарных чисел, у LongAdder есть методы increment() и add(). Но вместо того, чтобы складывать числа сразу, он просто хранит у себя набор слагаемых, чтобы уменьшить взаимодействие между потоками. Узнать результат можно с помощью вызова sum() или sumThenReset(). Этот класс используется в ситуациях, когда добавлять числа приходится гораздо чаще, чем запрашивать результат (часто это какие-то статистические исследование, например подсчёт количества запросов). Несложно догадаться, что, давая прирост в производительности, LongAdder требует гораздо большего количества памяти из-за того, что он хранит все слагаемые.</p><h3>LongAccumulator</h3><p>Класс LongAccumulator несколько расширяет возможности LongAdder. Вместо простого сложения он обрабатывает входящие значения с помощью лямбды типа LongBinaryOperator, которая передаётся при инициализации. Выглядит это так:</p><p>В этом примере при каждом вызове accumulate() значение аккумулятора увеличивается в два раза, и лишь затем суммируется с i. Так же, как и LongAdder, LongAccumulator хранит весь набор переданных значений в памяти.</p><p><br />прим. переводчика На самом деле, пример не совсем корректный; согласно <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAccumulator.html">документации</a>, LongAccumulator не гарантирует порядка выполнения операций. Корректной формулой была бы, например x+2*y, т.к. при любом порядке выполнения в конце будет получаться одно и то же значение.</p><h3>ConcurrentMap</h3><p>Интерфейс ConcurrentMap наследуется от обычного Map и предоставляет описание одной из самой полезной коллекции для конкурентного использования. Чтобы продемонстрировать новые методы интерфейса, мы будем использовать вот эту заготовку:</p><p>Метод forEach() принимает лямбду типа BiConsumer. Этой лямбде будут передаваться в качестве аргументов все ключи и значения таблицы по очереди. Этот метод может использоваться как замена for-each циклам с итерацией по всем Entry. Итерация выполняется последовательно, в текущем потоке. эту запятую не надо убирать. если вы считаете, что надо - пожалуйста, сначала скажите мне</p><p>Метод putIfAbsent() помещает в таблицу значение, только если по данному ключу ещё нет другого значения. Этот метод является потокобезопасным (о крайней мере, в реализации ConcurrentHashMap), поэтому вам не нужно использовать synchronized, когда вы хотите использовать его в нескольких потоках (то же самое справедливо и для обычного put()):</p><p>Метод getOrDefault() работает так же, как и обычный get(), с той лишь разницей, что при отсутствии значения по данному ключу он вернёт значение по-умолчанию, передаваемое вторым аргументом:</p><p>Метод replaceAll() принимает в качестве аргумента лямбда-выражение типа BiFunction. Этой лямбде по очереди передаются все комбинации ключ-значения из карты, а результат, который она возвращает, записывается соответствующему ключу в качестве значения:</p><p>Если же вам нужно изменить таким же образом только один ключ, это позволяет сделать метод compute():</p><p>Кроме обычного compute(), существуют так же методы computeIfAbsent() и computeIfPresent(). Они изменяют значение только если значение по данному ключу отсутствует (или присутствует, соответственно).</p><p>И, наконец, метод merge(), который может быть использован для объединения существующего ключа с новым значением. В качестве аргумента он принимает ключ, новое значение и лямбду, которая определяет, как новое значение должно быть объединено со старым:</p><h3>ConcurrentHashMap</h3><p>Кроме методов, которые описаны в ConcurrencyMap, в ConcurrentHashMap было добавлено и ещё несколько своих. Так же, как и параллельные stream’ы, эти методы используют специальный ForkJoinPool, доступный через ForkJoinPool.commonPool() в Java 8. Этот пул использует свои настройки для количества потоков, основанные на количестве ядер. У меня их 4, а значит использоваться будет три потока:</p><p>Это значение может быть специально изменено с помощью параметра JVM:</p><p>Мы рассмотрим три новых метода: forEach, search and reduce. У каждого из них есть первый аргумент, который называется parallelismThreshold, который определяет минимальное количество элементов в коллекции, при котором операция будет выполняться в нескольких потоках. Т.е. если в коллекции 499 элементов, а первый параметр выставлен равным пятистам, то операция будет выполняться в одном потоке последовательно. В наших примерах мы будем использовать первый параметр равным в единице, чтобы операции всегда выполнялись параллельно.</p><p>Для примеров ниже мы будем использовать всё ту же таблицу, что и выше (однако объявим её именем класса, а не интерфейса. чтобы нам были доступны все методы):</p><h4>ForEach</h4><p>Работает метод так же, как и в ConcurrentMap. Для иллюстрации многопоточности мы будем выводить названия потоков (не забывайте, что их количество для меня ограничено тремя):</p><h4>Search</h4><p>Метод search() принимает лямбда-выражение типа BiFunction, в которую передаются все пары ключ-значение по очереди. Функция должна возвращать null, если необходимое вхождение найдено. В случае, если функция вернёт не null, дальнейший поиск будет остановлен. Не забывайте, что данные в хэш-таблице хранятся неупорядоченно. Если вы будете полагаться на порядок, в котором вы добавляли данные в неё, вы можете не получить ожидаемого результата. Если условиям поиска удовлетворяют несколько вхождений, результат точно предсказать нельзя.</p><p>Или вот другой пример, который полагается только на значения:</p><h4>Reduce</h4><p>Метод reduce() вы могли уже встречать в Java 8 Streams. Он принимает две лямбды типа BiFunction. Первая функция преобразовывает пару ключ/значение в один объект (любого типа). Вторая функция совмещает все полученные значения в единый результат, игнорируя любые возможные null-значения.</p><p>На этом всё. Надеюсь, мои статьи были вам полезны ?</p>]]></content:encoded>
    </item>
    <item>
      <title>Обработка исключений в многопоточных приложениях</title>
      <link>https://tproger.ru/translations/multithreaded-exception-handling</link>
      <comments>https://tproger.ru/translations/multithreaded-exception-handling?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Курилкин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/multithreaded-exception-handling</guid>
      <description><![CDATA[<p>Поток, вернувший исключение из-за бага или испорченного файла, завершается незаметно для программы — Пол Шарф объясняет, что с этим делать.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/multithreaded-exception-handling">Обработка исключений в многопоточных приложениях</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 13 May 2016 13:43:10 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Пол Шарф, автор блога <a href="http://genericgamedev.com">genericgamedev.com</a></p><p><a href="http://genericgamedev.com/general/using-multi-threading-to-animate-and-speed-up-loading-screens/">В прошлом уроке</a> мы использовали многопоточность для создания анимированных (или даже интерактивных) экранов загрузки, а также для уменьшения времени загрузки в целом.</p><p>И хотя мы рассмотрели множество вещей на примере <a href="http://rochefusion.com/">Roche Fusion</a>, кое-что мы не затронули вовсе — что, если что-то пойдет не так? Или, более техническим языком — что, если один из наших потоков вернет исключение?</p><p>Если мы ничего не будем с этим делать, то поток, который вернет исключение (например, из-за бага или испорченного файла), завершится без нашего ведома, и мы никогда об этом не узнаем.</p><p>Так происходит, потому что исключения постепенно накапливаются в стеке вызовов до тех пор, пока не будут обработаны или не заполнят весь стек. В последнем случае поток, в котором появилось исключение, завершает работу.</p><p>Если проблема происходит в главном потоке, то пользователь просто увидит ошибку, но если речь идет о других потоках, то они при исключении завершаются без единого следа, и программа может повиснуть, ожидая успешного завершения работы аварийно остановившегося потока.</p><p>Заметьте, что ниже описанные решения этой проблемы применимы к любым многопоточным приложениям. Загрузочные экраны служат лишь в качестве примера.</p><h3>Возможные решения</h3><p>Существует несколько возможных способов обрабатывать исключения в потоках.</p><p>Например, главный поток может регулярно проверять, остановились ли другие и сделали ли они что-нибудь с возникшим исключением.</p><p>Проблема этого решения в том, что нам нужно быть в курсе всех выполняемых потоков, в ином случае какой-нибудь поток все равно экстренно завершится, не подав вида.</p><p>Альтернативно мы можем подписаться на глобальные события, происходящие во всем приложении, которые будут информировать нас о произошедших исключениях, так, что мы сможем реагировать на них.</p><p>Но и здесь не все так гладко. Такой подход не позволит нам «потерять» потоки, но из-за асинхронной природы приложения мы не можем быть уверены в том, когда исполнится код нашего обработчика, тем самым рискуя натолкнуться на другие проблемы многопоточности.</p><h3>Используем то, что у нас уже есть</h3><p>Ранее мы расписали архитектуру небольшого фреймворка, и я бы хотел представить вам решение, которое не приводит к вышеописанным проблемам.</p><p>В приложении, написанном нами в прошлом уроке, любой поток мог давать главному потоку работу на выполнение. Каждый поток может сам обрабатывать исключения таким образом, что все возникшие ошибки передавались бы главному потоку. В таком случае исключение либо заставит упасть все приложение, либо будет обработано должным образом. Вот простой пример реализации такой идеи:</p><p>actionQueue – это объект класса, разработанного нами <a href="http://genericgamedev.com/general/scheduling-cross-threaded-tasks-using-dotnets-blockingcollection/">в этом посте</a>, который позволяет перенаправлять из всех потоков какие-либо действия на главный поток.</p><p>Таким образом, когда threadAction возвращает исключение, оно не обрабатывается, а помещается в очередь на исполнение в главном потоке.</p><p>Когда главный поток доберется до исключения в очереди действий, он уже обработает его как надо, а если не сможет, то приложение просто закончит работу.</p><p>Данный подход отлично работает на первый взгляд, но есть одна проблема, которая очень быстро становится явной при отладке — при возвращении исключения, которое уже возвращалось ранее, его стек вызовов автоматически перезаписывается. И без начального стека вызовов мы даже не сможем определить, в какой части программы возникло исключение.</p><h3>Сохраняем стеки вызовов</h3><p>К счастью, у этой проблемы есть очень легкое решение.</p><p>Вместо того, чтобы возвращать то же исключение, что и раньше, мы можем просто создать новое, содержащее в себе старое, как это делает C#.</p><p>Внесем поправку в написанный выше код:</p><p>И это все, что нам нужно, чтобы убедиться в том, что мы не теряем информацию о предыдущем исключении при возвращении такого же нового.</p><h3>Дополнительно</h3><p>Конечно, если возможно, то лучше проектировать ваше приложение так, что возможность возникновения фатального исключения стремится к нулю.</p><p>Например, мы в Roche Fusion оборачиваем каждый скриптовый файл в try-catch. В случае, если что-то пойдет не так, в игровую консоль выводится предупреждение и загрузка продолжается.</p><p>В большинстве случаев это никак не влияет на игру, разве что некоторый контент может отображаться не так, как надо.</p><p>Однажды мы все-таки не обратили внимание на одно из предупреждений и напоролись на ситуацию, в которой при определенных обстоятельствах приложение могло крашиться из-за того, что один скрипт не загружался. Но в основном такой подход намного облегчает разработку и моддинг.</p><h3>Заключение</h3><p>Надеюсь, что эта статья дала вам представление о том, как надо обрабатывать возникающие исключения в многопоточных приложениях.</p><p>Если вас заинтересовали другие подходы, которые я упомянул, но о которых не рассказал подробнее, рекомендую поискать их онлайн.</p><p>Наслаждайтесь пикселями!</p>]]></content:encoded>
    </item>
    <item>
      <title>Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков</title>
      <link>https://tproger.ru/translations/java8-concurrency-tutorial-1</link>
      <comments>https://tproger.ru/translations/java8-concurrency-tutorial-1?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/java8-concurrency-tutorial-1</guid>
      <description><![CDATA[<p>Бенджамин Винтерберг на простых примерах показывает потоки, задачи и сервисы исполнителей из Concurrency API, появившегося ещё в Java 5.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/java8-concurrency-tutorial-1">Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 08 Jul 2015 13:58:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает <a href="http://winterbe.com">Бенджамин Винтерберг</a>, Software Engineer</p><p>Добро пожаловать в первую часть руководства по <a href="https://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D1%80%D0%B0%D0%BB%D0%BB%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F">параллельному программированию</a> в Java 8. В этой части мы на простых примерах рассмотрим, как выполнять код параллельно с помощью потоков, задач и сервисов исполнителей.</p><p>Впервые <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html">Concurrency API</a> был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть <a href="http://winterbe.com/posts/2014/03/16/java-8-tutorial/">мое руководство</a>.</p><h3>Потоки и задачи</h3><p>Все современные операционные системы поддерживают параллельное выполнение кода с помощью <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)">процессов</a> и <a href="https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%82%D0%BE%D0%BA_%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F">потоков</a>. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.</p><p><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html">Потоки</a> <i>(threads)</i> в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» <i>(task)</i>. Это делается через реализацию интерфейса Runnable, у которого есть только один метод без аргументов, возвращающий void — run(). Вот пример того, как это работает:</p><p>Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.</p><p>Результат выполнения этого кода может выглядеть так:</p><p>или так:</p><p>Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.</p><p>Потоки могут быть приостановлены на некоторое время. Это весьма полезно, если мы хотим сэмулировать долго выполняющуюся задачу. Например, так:</p><p>Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000).</p><p>Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.</p><p>Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей <i>(executor services)</i>.</p><h3>Исполнители</h3><p>Concurrency API вводит понятие сервиса-исполнителя <i>(ExecutorService)</i> — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.</p><p>Вот как будет выглядеть наш первый пример с использованием исполнителя:</p><p>Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.</p><p>Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown(), который ждет завершения запущенных задач, и shutdownNow(), который останавливает исполнитель немедленно.</p><p>Вот как я предпочитаю останавливать исполнителей:</p><p>Исполнитель пытается завершить работу, ожидая завершения запущенных задач в течение определенного времени (5 секунд). По истечении этого времени он останавливается, прерывая все незавершенные задачи.</p><h4>Callable и Future</h4><p>Кроме Runnable, исполнители могут принимать другой вид задач, который называется Callable. Callable — это также функциональный интерфейс, но, в отличие от Runnable, он может возвращать значение.</p><p>Давайте напишем задачу, которая возвращает целое число после секундной паузы:</p><p>Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.</p><p>После отправки задачи исполнителю мы сначала проверяем, завершено ли ее выполнение, с помощью метода isDone(). Поскольку задача имеет задержку в одну секунду, прежде чем вернуть число, я более чем уверен, что она еще не завершена.</p><p>Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true, и мы увидим на консоли следующее:</p><p>Задачи жестко связаны с сервисом исполнителей, и, если вы его остановите, попытка получить результат задачи выбросит исключение:</p><p>Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1), который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor(), однако мы можем изменить количество потоков в пуле.</p><h4>Таймауты</h4><p>Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:</p><p>Выполнение этого кода вызовет TimeoutException:</p><p>Вы уже, возможно, догадались, почему было выброшено это исключение: мы указали максимальное время ожидания выполнения задачи в одну секунду, в то время как ее выполнение занимает две.</p><h4>InvokeAll</h4><p>Исполнители могут принимать список задач на выполнение с помощью метода invokeAll(), который принимает коллекцию callable-задач и возвращает список из Future.</p><p>В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll. Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите <a href="http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/">мое руководство</a>.</p><h4>InvokeAny</h4><p>Другой способ отдать на выполнение несколько задач — метод invokeAny(). Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.</p><p>Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable, который вернет указанную строку после необходимой задержки:</p><p>Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:</p><p>В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool(). Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает <a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinPool.html">ForkJoinPool</a> с определенным параллелизмом <i>(parallelism size)</i>, по умолчанию равным количеству ядер машины.</p><p>ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком <i>(scheduled executors)</i>.</p><h3>Исполнители с планировщиком</h3><p>Мы уже знаем, как отдать задачу исполнителю и получить ее результат. Для того, чтобы периодически запускать задачу, мы можем использовать пул потоков с планировщиком.</p><p>ScheduledExecutorService способен запускать задачи один или несколько раз с заданным интервалом.</p><p>Этот пример показывает, как заставить исполнитель выполнить задачу через три секунды:</p><p>Когда мы передаем задачу планировщику, он возвращает особый тип Future — ScheduledFuture, который предоставляет метод getDelay() для получения оставшегося до запуска времени.</p><p>У исполнителя с планировщиком есть два метода для установки задач: scheduleAtFixedRate() и scheduleWithFixedDelay(). Первый устанавливает задачи с определенным интервалом, например, в одну секунду:</p><p>Кроме того, он принимает начальную задержку, которая определяет время до первого запуска.</p><p>Обратите внимание, что метод scheduleAtFixedRate() не берет в расчет время выполнения задачи. Так, если вы поставите задачу, которая выполняется две секунды, с интервалом в одну, пул потоков рано или поздно переполнится.</p><p>В этом случае необходимо использовать метод scheduleWithFixedDelay(). Он работает примерно так же, как и предыдущий, но указанный интервал будет отсчитываться от времени завершения предыдущей задачи.</p><p>В этом примере мы ставим задачу с задержкой в одну секунду между окончанием выполнения задачи и началом следующей. Начальной задержки нет, и каждая задача выполняется две секунды. Так, задачи будут запускаться на 0, 3, 6, 9 и т. д. секунде. Как видите, метод scheduleWithFixedDelay() весьма полезен, если мы не можем заранее сказать, сколько будет выполняться задача.</p><p>Это была первая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они доступны на <a href="https://github.com/winterbe/java8-tutorial">GitHub</a>. Можете смело форкать репозиторий и добавлять его в <a href="https://github.com/winterbe/java8-tutorial/stargazers">избранное</a>.</p><p>Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.</p><p>Перевод статьи «Java 8 Concurrency Tutorial: Threads and Executors»</p>]]></content:encoded>
    </item>
    <item>
      <title>10 советов по многопоточному программированию на Java</title>
      <link>https://tproger.ru/translations/10-java-multithread-practices</link>
      <comments>https://tproger.ru/translations/10-java-multithread-practices?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/10-java-multithread-practices</guid>
      <description><![CDATA[<p>Дж. Пол из блога Java Revisited собрал советы, которые помогают писать корректный параллельный код на Java и проверять его правильность без лишней боли.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/10-java-multithread-practices">10 советов по многопоточному программированию на Java</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 31 May 2015 11:35:08 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает Дж. Пол, автор блога Java Revisited</p><p>Написание параллельного кода – непростая задача, а проверка его корректности – задача еще сложнее. Несмотря на то, что Java предоставляет обширную поддержку многопоточности и синхронизации на уровне языка и API, на деле же оказывается, что написание корректного многопоточного Java-кода зависит от опыта и усердности конкретного программиста. Ниже изложен набор советов, которые помогут вам качественно повысить уровень вашего многопоточного кода на Java. Некоторые из вас, возможно, уже знакомы с этими советами, но никогда не помешает освежать их в памяти раз в пару лет.</p><p>Многие из этих советов появились в процессе обучения и практического программирования, а также после прочтения книг <a href="http://www.amazon.com/dp/0321349601/?tag=javamysqlanta-20">«Java concurrency in practice»</a> и <a href="http://www.amazon.com/dp/0321356683/?tag=javamysqlanta-20">«Effective Java»</a>. Я советую прочитать первую каждому Java-программисту два раза; да, всё правильно, ДВА раза. Параллелизм – запутанная и сложная для понимания тема (как, например, для некоторых – рекурсия), и после однократного прочтения вы можете не до конца всё понять.</p><p>Единственная цель использования параллелизма – создание масштабируемых и быстрых приложений, но при этом всегда следует помнить, что скорость не должна становиться помехой корректности. Ваша Java-програма должна удовлетворять своему инварианту независимо от того, запущена ли она в однопоточном или многотопочном виде. Если вы новичок в параллельном программировании, для начала ознакомьтесь с различными проблемами, возникающими при параллельном запуске программ (например: взаимная блокировка, состояние гонки, ресурсный голод и т.д.).</p><h3>1. Используйте локальные переменные</h3><p>Всегда старайтесь использовать локальные переменные вместо полей класса или статических полей. Иногда разработчики используют поля класса, чтобы сэкономить память и переиспользовать переменные, полагая, что создание локальной переменной при каждом вызове метода может потребовать большого количества дополнительной памяти. Одним из примеров такого использования может послужить коллекция (Collection), объявленная как статическое поле и переиспользуемая с помощью метода clear(). Это переводит класс в общее состояние, которого он иметь не должен, т.к. изначально создавался для параллельного использования в нескольких потоках. В коде ниже метод execute() вызывается из разных потоков, а для реализации нового функционала потребовалась временная коллекция. В оригинальном коде была использована статическая коллекция (List), и намерение разработчика были ясны — очищать коллекцию в конце метода execute(), чтобы потом можно было её заново использовать. Разработчик полагал, что его код потокобезопасен, потому что CopyOnWriteArrayList потокобезопасен. Но это не так — метод execute() вызывается из разных потоков, и один из потоков может получить доступ к данным, записанным другим потоком в общий список. Синхронизация, предоставляемая CopyOnWriteArrayList в данном случае недостаточна для обеспечения инвариантности метода execute().</p><p>Проблема: Данные одного сообщения попадут в другое, если два вызова execute() «пересекаются», т.е. первый поток добавит id из первого сообщения, затем второй поток добавит id из второго сообщения (это произойдёт ещё до очистки списка), таким образом данные одного из сообщений будут повреждены.</p><p>Варианты решений:</p><ol><li>Добавить <a href="http://java67.blogspot.sg/2013/01/difference-between-synchronized-block-vs-method-java-example.html">блок синхронизации</a> в ту часть кода, где поток добавляет что-то во временный список и очищает его. Таким образом, другой поток не сможет получить доступ к списку, пока первый не закончит работу с ним. В таком случае эта часть кода будет однопоточной, что уменьшит производительность приложения в целом.</li><li>Использовать локальный список в место поля класса. Да, это увеличит затраты памяти, но избавит от блока синхронизации и сделает код более читаемым. Также вам не придётся беспокоиться о временных объектах – о них позаботится сборщик мусора.</li></ol><p>Здесь представлен только один из случаев, но при написании параллельного кода лично я предпочитаю локальные переменные полям класса, если последних не требует архитектура приложения.</p><h3>2. Предпочитайте неизменяемые классы изменяемым</h3><p>Самая широко известная практика в многопоточном программировании на Java – использование неизменяемых (immutable) классов. Неизменяемые классы, такие как String, Integer и другие упрощают написание параллельного кода в Java, т.к. вам не придётся беспокоиться о состоянии объектов данных классов. <a href="http://javarevisited.blogspot.sg/2013/03/how-to-create-immutable-class-object-java-example-tutorial.html">Неизменяемые классы уменьшают количество элементов синхронизации в коде</a>. Объект неизменяемого класса, будучи однажды созданным, не может быть изменён. Самый лучший пример такого класса – строка (java.lang.String). Любая операция изменения строки в Java (перевод в верхний регистр, взятие подстроки и пр.) приведёт к созданию нового объекта String для результата операции, оставив исходную строку нетронутой.</p><h3>3. Сокращайте области синхронизации</h3><p>Любой код внутри области синхронизации не может быть исполнен параллельно, и если в вашей программе 5% кода находится в блоках синхронизации, то, согласно закону Амдала, производительность всего приложения не может быть улучшена более, чем в 20 раз. Главная причина этого в том, что 5% кода всегда выполняется последовательно. Вы можете уменьшить это количество, сокращая области синхронизации – попробуйте использовать их только для критических секций. Лучший пример сокращения областей синхронизации – блокировка с двойной проверкой, которую можно реализовать в Java 1.5 и выше с помощью volatile переменных.</p><h3>4. Используйте пул потоков</h3><p>Создание потока (Thread) — дорогая операция. Если вы хотите создать масштабируемое Java-приложение, вам нужно использовать пул потоков. Помимо тяжеловесности операции создания, управление потокам вручную порождает много повторяющегося кода, который, перемешиваясь с бизнес-логикой, уменьшает читаемость кода в целом. Управление потоками – задача фреймворка, будь то инструмент Java или любой другой, который вы захотите использовать. В JDK есть хорошо организованный, богатый и полностью протестированный фреймворк, известный как <a href="http://javarevisited.blogspot.sg/2013/07/how-to-create-thread-pools-in-java-executors-framework-example-tutorial.html">Executor framework</a>, который можно использовать везде, где потребуется пул потоков.</p><h3>5. Используйте утилиты синхронизации вместо wait() и notify()</h3><p>В Java 1.5 появилось множество утилит синхронизации, таких как CyclicBarrier, CountDownLatch и Semaphore. Вам всегда следует сначала изучить, что есть в JDK для синхронизации, прежде чем использовать wait() и notify(). Будет намного проще реализовать шаблон читатель-писатель с помощью BlockingQueue, чем через wait() и notify(). Также намного проще будет <a href="http://javarevisited.blogspot.sg/2012/07/countdownlatch-example-in-java.html">подождать 5 потоков</a> для завершения вычислений, используя CountDownLatch, чем реализовывать то же самое через wait() и notify(). Изучите пакет java.util.concurrent, чтобы писать параллельный код на Java лучшим образом.</p><h3>6. Используйте BlockingQueue для реализации Producer-Consumer</h3><p>Этот совет следует из предыдущего, но я выделил его отдельно ввиду его важности для параллельных приложений, используемых в реальном мире. Решение многих проблем многопоточности основано на шаблоне Producer-Consumer, и BlockingQueue – лучший способ реализации его в Java. В отличие от Exchanger, который может быть использовать в случае одного писателя и читателя, BlockingQueue может быть использована для правильной обработки нескольких писателей и читателей.</p><h3>7. Используйте потокобезопасные коллекции вместо коллекций с блокированием доступа</h3><p>Потокобезопасные коллекции предоставляют большую масштабируемость и производительность, чем их аналоги с блокированием доступа (Collections.synchronizedCollection и пр.). СoncurrentHashMap, которая, по моему мнению, является самой популярной потокобезопасной коллекцией, демострирует лучшую производителность, чем блокировочные HashMap или Hashtable, в случае, когда количество читателей превосходит количество писателей. Другое преимущество потокобезопасных коллекций состоит в том, что они реализованы с помощью нового механизма блокировки (java.util.concurrent.locks.Lock) и используют нативные механизмы сихнронизации, предоставленные низлежащим аппаратным обеспечением и JVM. Вдобавок используйте CopyOnWriteArrayList вместо Collections.synchronizedList, если чтение из списка происходит чаще, чем его изменение.</p><h3>8. Используйте семафоры для создания ограничений</h3><p>Чтобы создать надёжную и стабильную систему, у вас должны быть ограничения на ресурсы (базы данных, файловую систему, сокеты и т.д.). Ваш код ни в коем случае не должен создавать и/или использовать бесконечное количество ресурсов. Семафоры (java.util.concurrent.Semaphore) — хороший выбор для создания ограничений на использование дорогих ресурсов, таких как подключения к базе данных (кстати, в этом случае можно использовать пул подключений). Семафоры помогут создать ограничения и заблокируют потоки в случае недоступности ресурса.</p><h3>9. Используйте блоки синхронизации вместо блокированных методов</h3><p>Данный совет расширяет совет по сокращению областей синхрониации. Использование блоков синхронизации – один из методов сокращения области синхронизации, что также позволяет выполнить блокировку на объекте, отличном от текущего, представленного указателем this. Первым кандитатом должна быть атомарная переменная, затем volatile переменная, если они удовлетворяют ваши требования к синхронизации. Если вам требуется взаимное исключение, используйте в первую очередь ReentrantLock, либо блок synchronized. Если вы новичок в параллельном программировании, и не разрабатываете какое-либо жизненно важное приложение, можете просто использовать блок synchronized — так будет безопаснее и проще.</p><h3>10. Избегайте использования статических переменных</h3><p>Как показано в первом совете, статические переменные, будучи использованными в параллельном коде, могут привести к возникновению множества проблем. Если вы всё-таки используете статическую переменную, убедитесь, что это константа либо неизменяемая коллекция. Если вы думаете о том, чтобы переиспользовать коллекцию с целью экономии памяти, вернитесь ещё раз к первому совету.</p><h3>11. Используйте Lock вместо synchronized</h3><p>Последний, бонусный совет, следует использовать с осторожностью. Интерфейс Lock — мощный инструмент, но его сила влечёт и большую ответственность. Различные объекты Lock на операции чтения и записи позволяют реализовывать масштабируемые структуры данных, такие как ConcurrentHashMap, но при этом требуют большой осторожности при своём программировании. В отличие от блока synchronized, поток не освобождает блокировку автоматически. Вам придётся явно вызывать unlock(), чтобы снять блокировку. Хорошей практикой является вызов этого метода в блоке finally, чтобы блокировка завершалась при любых условиях:</p><h3>Заключение</h3><p>Вам были представлены советы по написанию многопоточного кода на Java. Ещё раз повторюсь, никогда не помешает перечитывать «Java concurrency in practice» и «Effective Java» время от времени. Также можно вырабатывать нужный для параллельного програмирования способ мышления, просто читая чужой код и пытаясь визуализировать проблемы во время разработки. В завершение спросите себя, каких правил вы придерживаетесь, когда разрабатываете многопоточные приложения на Java?</p><p>Перевод статьи Top 10 Java Multithreading and Concurrency Best Practices</p>]]></content:encoded>
    </item>
    <item>
      <title>В чем разница между потоком и процессом?</title>
      <link>https://tproger.ru/problems/what-is-the-difference-between-threads-and-processes</link>
      <comments>https://tproger.ru/problems/what-is-the-difference-between-threads-and-processes?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/problems/what-is-the-difference-between-threads-and-processes</guid>
      <description><![CDATA[<p>Процесс — экземпляр программы со своими ресурсами и адресным пространством, доступ к чужим данным идёт только через межпроцессное взаимодействие.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/problems/what-is-the-difference-between-threads-and-processes">В чем разница между потоком и процессом?</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Задачки]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sun, 01 Feb 2015 19:40:17 GMT</pubDate>
      <content:encoded><![CDATA[<p>Процессы и потоки связаны друг с другом, но при этом имеют существенные различия.</p><p>Процесс — экземпляр программы во время выполнения, независимый объект, которому выделены системные ресурсы (например, процессорное время и память). Каждый процесс выполняется в отдельном адресном пространстве: один процесс не может получить доступ к переменным и структурам данных другого. Если процесс хочет получить доступ к чужим ресурсам, необходимо использовать межпроцессное взаимодействие. Это могут быть конвейеры, файлы, каналы связи между компьютерами и многое другое.</p><p>Поток использует то же самое пространства стека, что и процесс, а множество потоков совместно используют данные своих состояний. Как правило, каждый поток может работать (читать и писать) с одной и той же областью памяти, в отличие от процессов, которые не могут просто так получить доступ к памяти другого процесса. У каждого потока есть собственные регистры и собственный стек, но другие потоки могут их использовать.</p><p>Поток — определенный способ выполнения процесса. Когда один поток изменяет ресурс процесса, это изменение сразу же становится видно другим потокам этого процесса.</p><p>Разбор взят из книги Гейл Л. Макдауэлл «Cracking the Coding Interview» (есть в переводе).</p>]]></content:encoded>
    </item>
  </channel>
</rss>