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

推荐订阅源

Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
量子位
腾讯CDC
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
The Hacker News
The Hacker News
T
The Exploit Database - CXSecurity.com
B
Blog
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
T
Threatpost
博客园 - 聂微东
T
Tailwind CSS Blog
The Last Watchdog
The Last Watchdog
C
Check Point Blog
N
Netflix TechBlog - Medium
D
DataBreaches.Net
爱范儿
爱范儿
IT之家
IT之家
S
Secure Thoughts
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
C
Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
有赞技术团队
有赞技术团队
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
Schneier on Security
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
雷峰网
雷峰网
Project Zero
Project Zero
博客园 - Franky
H
Heimdal Security Blog
A
About on SuperTechFans
Security Latest
Security Latest
Webroot Blog
Webroot Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 миллионов точек без потерь
Vulkan API и асинхронность. Введение. Требования к архитектуре
shagunov · 2026-05-14 · via Все публикации подряд на Хабре

Приветствие

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

Данный цикл не является туториалом, а лишь моей попыткой исследования основных аспектов асинхронного программирования на Vulkan API. Целью данного исследования - разработать архитектуру, упрощающую управление многопоточным выполнением, в основу которой будет положено использование корутин c++20.

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

Введение

Прежде, чем погружаться в тему, необходимо обозначить мотивацию применения асинхронности и параллелизма. Важно отметить то, что это разные понятия.

Асинхронность — модель взаимодействия, при которой момент инициирования операции отделён от момента получения её результата; результат доставляется позднее через callback/future/promise/poll/awaitable.

Параллелизм - модель выполнения, при которой разные задачи исполняются на разных вычислительных ресурсах.

Хотя асинхронность и параллелизм и являются различными понятиями, тем не менее, в основе реализации асинхронности может лежать параллелизм.

Мотивация использования асинхронности обусловлена следующим образом. Для ряда компонентов критичен интервал между итерациями основного цикла обработки событий (tick interval); в частности, главный цикл событий не должен блокироваться продолжительным ожиданием операции рендеринга, поскольку это увеличивает задержку обработки событий (latency) и ухудшает восприятие отзывчивости интерфейса (UX). Если скорость поступления событий превышает скорость их обработки, очередь растёт и задержки накапливаются — в системах с ограниченным буфером это может приводить к потере событий. Многие этапы рендера (подготовка command buffers, culling, staging uploads) можно выполнять параллельно, тогда как submit → GPU execute → present требуют сохранения порядка. Перенос подготовки или других тяжёлых задач в рабочий поток уменьшает нагрузку главного цикла обработки событий, но требует явной синхронизации и аккуратного управления временем жизни ресурсов (см. раздел о примитивах синхронизации).

Рис. 1. Главный цикл обработки запускает синхронную процедуру. Если она долгая, это может привести к значительной задержке итерации цикла.

Рис. 1. Главный цикл обработки запускает синхронную процедуру. Если она долгая, это может привести к значительной задержке итерации цикла.

Рис. 2. Главный цикл запускает асинхронную операцию. В данном случае она выполняется в другом потоке. Когда выполнение завершается, асинхронная процедура может отправить, например, событие о завершении в главный цикл сообщений. Таким образом задержка итерации цикла минимальна.

Рис. 2. Главный цикл запускает асинхронную операцию. В данном случае она выполняется в другом потоке. Когда выполнение завершается, асинхронная процедура может отправить, например, событие о завершении в главный цикл сообщений. Таким образом задержка итерации цикла минимальна.

Таким образом, асинхронность позволяет эффективно использовать многопоточную архитектуру. Однако следует внимательно следить за изменяемыми объектами, которыми могут управлять несколько потоков. Это делается либо при помощи мьютексов, либо при помощи специальных структур данных, таких, как lock-free очереди. Состояние исполнения, при котором конкурентный (одновременный) доступ к разделяемым данным приводит к неопределённому поведению, называется состоянием гонки (англ. race condition).

1. Модель исполнения Vulkan API

В отличие от OpenGL, Vulkan API не использует глобальный текущий контекст для потока. Вместо этого состояние контекста исполнения команд инкапсулировано в специальные объекты. Как правило, эти объекты являются неизменяемыми. Иными словами, если, например, необходимо изменить состояние конвейера рендеринга, следует создать новый объект VkPipeline. Это позволяет безопасно использовать эти объекты в разных потоках.

Тем не менее, существуют такие команды, которые могут изменять внутреннее состояние некоторых объектов. В таком случае разработчику требуется обеспечить для них внешнюю синхронизацию.

Внешняя синхронизация (External Synchronisation) - это требование спецификации Vulkan API для некоторых команд, применительно к тем параметрам, которые могут изменять своё состояние. Она должна предотвращать любой конкурентный доступ к данному параметру во время исполнения указанной команды.

Итак, для описания модели исполнения Vulkan API мы будем руководствоваться разделом Threading Behavior спецификации. Этот раздел содержит перечисление всех команд, которые принимают аргументы для которых требуется внешняя синхронизация.

Весь список команд можно разделить на две категории:

  1. Команды хоста. Это синхронные вызовы. Как правило, это запросы к драйверу Vulkan API. К ним относятся, например, команды создания и удаления объектов, использования очереди устройства, работа с памятью и так далее. Существует расширение VK_KHR_deferred_host_operations, которое позволяет выполнять асинхронные вызовы на стороне хоста, но это специфично для весьма ограниченного набора задач.

  2. Команды устройства. Это асинхронные вызовы. Это непосредственно те команды, которые выполняет устройство. К ним относится рендеринг, копирование данных, вычисления, показ. Такие команды требуют очереди для отправки команд, а также командные буфера для управления последовательностью задач. Для ожидания используют специальные примитивы синхронизации (VkFence).

2. Механизмы синхронизации

Множество команд в Vulkan API являются асинхронными. Кроме того ресурсы, используемые в командах, выполняемых на устройстве, также могут требовать синхронизации. Для этого Vulkan API предоставляет примитивы синхронизации.

  1. Для синхронизации между командами внутри одного командного буфера используют барьеры памяти. Барьеры памяти обеспечивают видимость операций для новых. Например, барьеры могут использоваться для того, чтобы все операции чтения из текстуры предшествовали окончанию рендеринга в него.

  2. Для синхронизации между разными Submit'ами используются объекты VkSemaphore. Например, они могут обеспечить порядок операций запись в изображение → использование в качестве текстуры.

  3. Для синхронизации между устройством и хостом используются объекты VkFence. Они используются тогда, когда нужно дождаться завершения выполнения команд на стороне хоста. Например, если требуется дождаться готовности рендеринга мы используем fence, чтобы убедиться в том, что командный буфер выполнился и мы сможем писать в него команды для отрисовки нового кадра.

  4. Для особых случаев синхронизации может использоваться также VkEvent. Его использование не так часто требуется, поэтому мы его рассматривать не будет.

В версии 1.2 или с расширением VK_KHR_timeline_semaphore появились timeline semaphores. Это позволяет для последовательно идущих операций использовать один семафор. Например, для рендеринга в swapchain можно использовать один семафор для всех операций рендеринга, а затем использовать vkWaitSemaphores для ожидания завершения всех операций. Это позволяет упростить код и избежать необходимости создавать множество семафоров для каждой операции.

Избегайте чрезмерной синхронизации, особенно использования VKFence. Он полезен именно только тогда, когда ожидание завершения операции на устройстве является необходимым разграничением для избежания состояния гонок, а также если действительно необходимо обеспечить строгий порядок операций. Это лишает преимуществ параллелизма, поскольку превращает асинхронный код в последовательность. Отдавайте приоритет барьерам и семафорам, если это возможно. Так вы сможете не только сократить использование VkFence, но и минимизировать количество вызовов vkQueueSubmit. Помните, что каждый вызов Submit — это прямое обращение к драйверу, которое является дорогостоящей операцией. Группировка команд и использование внутренних механизмов синхронизации GPU позволяют максимально эффективно использовать ресурсы хоста.

3. Требования к внешней синхронизации

Здесь начинается самое интересное. Я настоятельно рекомендую вам обратиться к разделу 3.6 спецификации, чтобы просмотреть список команд. Он разделён на три списка

  1. Команды, требующие синхронизации параметров или членов структур, которые передаются в качестве параметров вызова.

  2. Команды, требующие синхронизации параметров элементов массива параметров.

  3. Команды, неявно требующие синхронизации объектов, которые связаны с передаваемыми параметрами.

Весь список можно разделить на следующие категории.

  • Очереди команд (VkQueue):

    • Сложность: Отправка командных буферов на исполнение (vkQueueSubmit), sparse binding, операции показа (vkQueuePresentKHR) и другие команды, изменяющие состояние очереди, требуют внешней синхронизации.

    • Проблема: Ограниченное количество очередей на устройстве приводит к конкуренции за их использование в многопоточной среде. Невозможно создать новую очередь без пересоздания всего устройства, что делает их критическим общим ресурсом.

  • Пулы команд и командные буферы (VkCommandPool, VkCommandBuffer):

    • Сложность: Запись команд в буферы (vkCmd…), сброс (vkResetCommandBuffer) и выделение новых командных буферов (vkAllocateCommandBuffers), а также сброс пулов (vkResetCommandPool) требуют внешней синхронизации.

    • Проблема: Командные буферы привязаны к своему пулу. Для параллельной записи команд в разных потоках необходимо использовать буферы, выделенные из разных пулов команд (или из пулов, созданных с флагом VK_COMMAND_POOL_CREATE_TRANSIENT_BIT и VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT).

  • Примитивы синхронизации (фенсы, семафоры, события):

    • Сложность: Использование примитивов синхронизации (например, VkFence для ожидания завершения операций) требует внешней синхронизации.

    • Проблема: Нельзя, например, использовать один и тот же VkFence для ожидания нескольких задач одновременно без риска состояний гонки или неопределенного поведения. Сброс состояния примитивов также требует синхронизации.

  • Память устройства (VkDeviceMemory):

    • Сложность: Операции отображения (vkMapMemory), отмены отображения (vkUnmapMemory), а также освобождения памяти (vkFreeMemory) требуют внешней синхронизации.

  • Ресурсы (буферы и изображения VkBuffer, VkImage):

    • Сложность: Привязка ресурсов к памяти (vkBindBufferMemory, vkBindImageMemory), а также их удаление (vkDestroyBuffer, vkDestroyImage) требуют внешней синхронизации.

  • Операции показа (VkSwapchainKHR, VkSurfaceKHR):

    • Сложность: Все операции, связанные с презентацией изображения на экран, такие как получение следующего изображения (vkAcquireNextImageKHR) и его показ (vkQueuePresentKHR), требуют внешней синхронизации.

    • Рекомендация: Из-за их критичности и частой зависимости от оконной системы, эти операции часто рекомендуется выполнять в главном потоке приложения.

  • Дескрипторы (VkDescriptorPool, VkDescriptorSet):

    • Сложность: Выделение новых дескрипторных наборов (vkAllocateDescriptorSets), их уничтожение (vkFreeDescriptorSets), обновление содержимого (vkUpdateDescriptorSets), а также очистка пулов (vkResetDescriptorPool) требуют внешней синхронизации.

  • Завершение работы устройства:

    • Сложность: Команды ожидания завершения всех операций на устройстве (vkDeviceWaitIdle) или в конкретной очереди (vkQueueWaitIdle) требуют внешней синхронизации для всех связанных объектов.

  • Удаление объектов (vkDestroy…):

    • Сложность: Удаление любого объекта Vulkan должно осуществляться только тогда, когда этот объект не используется ни в каких активных операциях на GPU и не требуется для будущих команд.

    • Проблема: Несвоевременное удаление может привести к неопределенному поведению, крашам или утечкам памяти.

4. Требования к архитектуре

Просмотрев все нюансы, которые следует учитывать, опишем требования к нашей архитектуре.

  1. Отсутствие блокировок. Блокировки могут блокировать потоки из пула. Недопустима такая ситуация, когда все потоки из пула оказываются в состоянии блокировки. Вместо того чтобы блокировать поток для ожидания, мы будем переключаться на новую задачу.

  2. Требуется обеспечить эксклюзивный доступ к объектам, для которых требуется внешняя синхронизация.

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

  4. Архитектура должна учитывать некоторые ограничения, которые накладывает использование API (Например, ограниченное количество очередей).

5. Задачи, решаемые предлагаемой архитектурой

Учитывая сложности, связанные с ручным управлением внешней синхронизацией в Vulkan API, а также ограничения традиционных подходов к асинхронному программированию, перед нами стоят следующие ключевые задачи при разработке новой архитектуры:

  1. Создание потокобезопасных структур данных: Разработка и реализация структур данных, которые обеспечат безопасный и эффективный конкурентный доступ к ресурсам Vulkan, требующим внешней синхронизации. Это позволит абстрагироваться от низкоуровневых механизмов защиты (мьютексов, lock-free очередей) и сосредоточиться на логике приложения.

  2. Реализация неблокирующего ожидания примитивов синхронизации: Разработка специализированных awaitable-типов для корутин C++20, которые позволят неблокирующе ожидать завершения операций, сигнализируемых примитивами синхронизации Vulkan (такими как VkFence, VkSemaphore или VkEvent). Это позволит потокам CPU эффективно переключаться на другие задачи вместо блокирующего ожидания GPU.

  3. Формулирование условий неблокирующего ожидания: Определение и реализация механизмов для формулирования и неблокирующего ожидания завершения асинхронных операций Vulkan, требующих внешней синхронизации. Цель — избежать блокировки потоков CPU и позволить им выполнять другие полезные задачи.

  4. Разработка корутин для упрощения работы: Проектирование и внедрение специализированных корутин C++20, которые позволят значительно упростить написание асинхронного кода для Vulkan API. Это сделает код более линейным, читаемым, поддерживаемым и менее подверженным ошибкам, связанным с ручной синхронизацией.

Заключение вводной статьи

Как можно убедиться, фронт работ по созданию эффективной и безопасной асинхронной архитектуры для Vulkan API довольно-таки обширен. В рамках данного цикла статей мы будем поэтапно разбирать поставленные задачи, чтобы в конечном итоге объединить мощь корутин C++20 с низкоуровневым контролем Vulkan API.

В следующей статье мы сосредоточимся на первой задаче из нашего списка: создании потокобезопасных структур данных для конкурентного доступа к ресурсам Vulkan. Мы подробно рассмотрим существующие ограничения и возможности, которые могут быть использованы для проектирования нашей архитектуры.

Благодарю за внимание и до встречи в следующей части!