惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
月光博客
月光博客
AI
AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
GbyAI
GbyAI
L
Lohrmann on Cybersecurity
O
OpenAI News
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
L
LINUX DO - 最新话题
人人都是产品经理
人人都是产品经理
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
The Hacker News
The Hacker News
Y
Y Combinator Blog
Latest news
Latest news
SecWiki News
SecWiki News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cisco Blogs
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Многопоточное программирование возвращается
Кибер-Странник · 2026-06-22 · via Все публикации подряд на Хабре

Многопоточное программирование возвращается

Средний

8 мин

109

Привет, мой вожделенный хабравчанин, хабраюзер, хабражитель или просто IT‑специалист — это как тебе удобнее называться. В общем, большой и горячий привет!

Читал ли ты мою предыдущую статью «Ох уж это многопоточное программирование»? Если да, то ты можешь со спокойной душой читать мою статью. Если же нет, то рекомендую сначала прочитать ту предыдущую статью, и уже потом приниматься за этот «десерт».

Ещё кое‑что о синхронизации потоков

Семафоры и их теория

Как я писал ранее, для синхронизации потоков в Win32 API (и не только) предназначено много всяких интересных технологий: критические секции, мьютексы и так далее. Среди этих штук есть технология семафоров.

Семафор представляет собой счётчик, используемый несколькими потоками для синхронизации. По умолчанию он может быть равен 0 или какому‑нибудь положительному (но только не отрицательному!) числу.

Для работы с семафорами нужны 2 функции: down и up. Первая функция проверяет, равен ли семафор 0. Если нет, то функция уменьшает значение семафора на 1 и поток продолжает работу. Если же да, то поток приостанавливается и начинает терпеливо ждать, пока значение семафора не увеличится и ему не выпадет счастливый шанс продолжить своё выполнение, завершив работу функции down. Функция up вне зависимости от значения семафора увеличивает его на 1. Если с этим семафором связаны один или несколько ожидающих потоков, то по определённому алгоритму выбирается один из таких потоков, а дальше этому потоку предоставляется уникальный шанс продолжить свою работу. В этом случае значение семафора как было, так и останется равно 0, но зато кол‑во ожидающих потоков уменьшится на 1.

Псевдокод, реализующий семафоры
typedef volatile int semaphore; // Объявляем о таком новом типе переменных, как семафоры

void down(semaphore *s){
  if(*s == 0){ // Семафор равен 0?
    lock(); // Если да, то заблокироваться
  }	
  *s--; // Уменьшить значение семафора на 1
}

void up(semaphore *s){
  unlock(); // Разблокировать один из потоков (например, случайным образом или по алгоритму FIFO)
  *s++; // Увеличить значение семафора на 1
}

Нужно отметить, что все операции с объектами синхронизации (вход и выход для критических секций, блокировка и разблокировка для мьютексов, up и down для семафоров и так далее) должны выполняться атомарно (неделимо). Т. е. если мы, например, увеличиваем семафор на 1 и тем самым освобождаем один из потоков от долгого и мучительного ожидания через какой‑либо API, то этот самый API гарантирует нам то, что выполнение нашего потока не будет прервано между операциями увеличения семафора и освобождения одного из потоков. Если такие операции не будут выполняться атомарно (неделимо), то вся синхронизация потеряет смысл.

Семафоры и их реализация в Win32 API

С семафорами в Win32 API особо заморачиваться не надо. Вот 2 функции для управления ими:

HANDLE CreateSemaphore (LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCSTR lpName);
WINBOOL ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);

В 1-й функции lpSemaphoreAttributes — указатель на структуру атрибутов безопасности, lInitialCount — значение, которое присваивается семафору в самом начале его «жизни», lMaximumCount — максимальное значение для семафора (оно обязательно должно быть больше 0!), а lpName позволяет сделать семафор именованным — так же, как и в случае с мьютексами. Если в этом параметре для имени указать NULL, то семафор будет безымянным.

2-я функция позволяет увеличить значение семафора (аналог нашей псевдофункции up в предыдущем примере). hSemaphore — это, конечно же, дескриптор семафора. Заметь, что в этой функции можно увеличить значение семафора не только на 1, но и на 2, на 3 и так далее. Таким образом можно освободить сразу несколько потоков, но при этом не стоит забывать про максимальное значение семафора (ещё не забыл про параметр lMaximumCount?). Значение увеличения настраивается параметром lReleaseCount. lpPreviousCount является указателем на переменную, в которую будет помещено предыдущее значение семафора. Но, возможно, для тебя это будет пока лишним — можешь указать NULL.

Выполнить нашу псевдофункцию down в «оригинальной обёртке от Microsoft» можно, опять‑таки, через уже знакомую нам функцию WaitForSingleObject. Вот же выскочка эта функция! Везде она нам себя предлагает!

Чтобы уменьшить значение семафора на 1 (и, возможно, подождать до «лучших времён»), надо просто вызвать эту функцию, передав ей дескриптор семафора в качестве 1-ого параметра. Готово.

События, их теория и реализация в Win32 API

События используются для доставки потокам какого‑либо сообщения, например «У нашей компании резко выросли доходы — обновить базу данных!» или «Один рабочий поток жёстко завис и не отвечает — свистать всех наверх!». События бывают 2-х типов: с ручным и автоматическим сбросом.

Чтобы работать с событиями, в Win32 API есть следующие функции:

HANDLE CreateEvent (LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL bManualReset, WINBOOL bInitialState, LPCSTR lpName)
WINBOOL SetEvent (HANDLE hEvent)
WINBOOL ResetEvent (HANDLE hEvent)
WINBOOL PulseEvent (HANDLE hEvent)

В 1-й функции lpEventAttributes — атрибуты безопасности для события, bManualReset определяет тип сброса события, а lpName позволяет сделать событие именованным (или безымянным, если указан NULL). bInitialState мы рассмотрим ниже.

Примечание: все имена объектов синхронизации в Win32 API вносятся в единый «реестр» имён, так что не пытайся создать мьютекс «Объект синхронизации» и семафор «Объект синхронизации».

Вот пример использования событий. Представь себе многопоточную систему, управляющую сервером Хабра. Через функцию CreateEvent создаётся событие NewArticle. Оно сообщает о том, что к какому‑либо автору на Хабре пришла муза, и он написал гениальную статью, а затем ещё и отправил её на сервер Хабра для того, чтобы эту гениальную статью могли прочитать все‑все‑все. Но пока никаких новых статей нет. Потоки‑рабочие вызывают функцию WaitForSingleObject с дескриптором NewArticle, выстраиваются в очередь и начинают ждать, пока на сервер не отправится новая статья. Как только на сервере появится новая статья, готовая к публикации, поток‑хозяин (или ещё кто‑нибудь) вызывает функцию SetEvent (а потом — ResetEvent) для того, чтобы перевести событие в сигнальное состояние и освободить сразу все рабочие потоки либо функцию PulseEvent для того, чтобы вызвать сразу последовательность функций SetEvent и ResetEvent. Если NewArticle является событием с автосбросом, то после вызова функции SetEvent вызывать функцию ResetEvent не надо — событие в любом случае возобновит работу лишь 1-ого потока. Значение bInitialState в функции CreateEvent позволяет создать событие сразу в сигнальном состоянии.

Примечание: в наше время использовать функцию PulseEvent не рекомендуется, так как она может случайно заблокировать поток навсегда.

Мьютексы в библиотеке Pthreads

В POSIX‑совместимой библиотеке Pthreads тоже есть мьютексы. Вот все функции для работы с ними:

int pthread_mutex_lock(pthread_mutex_t *m)
int pthread_mutex_timedlock(pthread_mutex_t *m, const struct timespec *ts)
int pthread_mutex_unlock(pthread_mutex_t *m)
int pthread_mutex_trylock(pthread_mutex_t *m)
int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a)
int pthread_mutex_destroy(pthread_mutex_t *m)

1-я функция позволяет заблокировать мьютекс, 2-я может заблокировать мьютекс с ограничением по времени (то есть если мьютекс свободен, поток сразу захватывает его, если мьютекс захвачен, поток блокируется только на определённый промежуток времени), 3-я — разблокировать мьютекс, 4-я — попробовать заблокировать мьютекс, 5-я — создать мьютекс и 6-я — уничтожить его. Во всех функциях параметр m является указателем на переменную типа pthread_mutex_t.

Последствия синхронизации

Даже очень умно продуманная синхронизация для потоков может преподнести нежданные «подарочки». Представь себе 2 потока. Они хотят получить доступ к компакт‑диску и принтеру, используя мьютексы для синхронизации. 1-й поток действует по своему порядку захвата компакт‑диска и принтера, а 2-й — по своему. Ниже приведён их код на C++ с использованием Win32 API:

Код потоков
DWORD WINAPI FirstThread (LPVOID){
  WaitForSingleObject(CD, INFINITE);
  WaitForSingleObject(Printer, INFINITE);
  UseCD();
  UsePrinter();
  ReleaseMutex(Printer);
  ReleaseMutex(CD);
}

DWORD WINAPI SecondThread (LPVOID){
  WaitForSingleObject(Printer, INFINITE);
  WaitForSingleObject(CD, INFINITE);
  UseCD();
  UsePrinter();
  ReleaseMutex(CD);
  ReleaseMutex(Printer);
}

Предположим, что 1-й поток может завладеть компакт‑диском (и делает это), но у него заканчивается квант времени и управление передаётся 2-му потоку. 2-й поток захватывает принтер и тоже передаёт управление 1-му потоку. Теперь 1-й поток пытается получить доступ к принтеру, но блокируется из‑за того, что он уже принадлежит 2-му потоку, а 2-й поток пытается получить доступ к компакт‑диску, но тоже блокируется — компакт‑диск уже принадлежит 1-му потоку. Теперь потоки будут заблокированы навсегда. Эта ситуация называется взаимоблокировкой, её схема показана ниже.

Схема взаимоблокировки
Схема простейшей взаимоблокировки

Схема простейшей взаимоблокировки

Как видишь, стрелки на схеме взаимоблокировки (или, по‑научному, на графе ресурсов) образуют цикл в виде знака бесконечности. Соответственно, потоки будут ждать по‑настоящему бесконечно.

«Взаимоблокировка в группе процессов возникает в том случае, если каждый процесс из этой группы ожидает события, наступление которого зависит исключительно от другого процесса из этой же группы» — пишет Э. Таненбаум в своей книге «Современные операционные системы». В нашем случае вместо процессов — потоки, но суть дела это не меняет.

В состояние взаимоблокировки могут попасть сколько угодно потоков и ресурсов, причём каких угодно ресурсов — семафоров, мьютексов, критических секций, событий и так далее. Главное, чтобы ресурс являлся синхронизированным через ожидание. Взаимоблокировки — отдельная «отрасль» многопоточного программирования, про которую много чего написано.

Наша только что описанная взаимоблокировка именуется ресурсной взаимоблокировкой. Но кроме ресурсных взаимоблокировок, есть ещё и другие взаимоблокировки — коммуникационные (взаимоблокировки при обмене данными, например, через интернет) и активные (потоки/процессы в принципе продолжают работать после возникновения взаимоблокировки, но ситуация всё равно не получает никакого развития).

Заключение

Мы подходим к логическому концу статьи. С какими только технологиями мы не знакомились! Критические секции, семафоры, мьютексы, события, Pthreads — всё это мы уже осмотрели. Я надеюсь, ты усвоил весь материал по многопоточному программированию и готов использовать его на практике. Но, конечно, писать и проектировать многопоточные программы достаточно непросто. Так что если есть возможность написать однопоточную программу вместо многопоточной, то лучше всё‑таки писать однопоточную.

Ой, а ведь кроме потоков, есть ещё и нити…

Всё, хватит. Больше не надо.

Небольшой постскриптум

P. S. Если в твоей программе потоки используют общие (глобальные) переменные, то этим переменным просто необходимо присвоить специальное ключевое слово volatile, например volatile int N; , иначе среди потоков может случиться хаос — значение общей переменной без ключевого слова volatile может быть разным для каждого потока из‑за того, что компилятор в целях оптимизации может засунуть переменную не в память, а в регистр процессора.