Ориентация главы
Группа задач (xtaskgroup) — структурный параллелизм в полной форме: одно множество задач — одна точка ожидания (xrtTaskGroupWait — больше не ждать каждый Future отдельно), сводная статистика финалов разом (xtaskgroupstats — полный перечень успех/отказ/отмена/закрыто/отклонено), области родитель-потомок (xrtTaskGroupChild — структурная форма дерева отмены: отмена родительской группы = запрос всему поддереву; финал листа всё равно подтверждает производящая сторона). Токены главы 54, Future главы 58 и пул главы 59 сходятся здесь — глава собирает россыпные параллельные инструменты в программные единицы с границами жизненного цикла.
60.1引入
Код «ждать пачку» без групп задач: 20 Future в массив, Wait по одному или цикл со счётчиком, ручная статистика успехов и отказов, при отказе одной отменить остальные — опять ручная пересылка токенов; сорок строк шаблона, переписываемых на каждом параллельном сведении, и каждая пропущенная ветвь (отмена — это отказ?) — баг статистики.
Группа задач сворачивает сорок строк в одно понятие. Важнее структурность: у параллельных операций есть границы областей — группа создаётся на входе, все задачи внутри принадлежат ей, на выходе из области группа ждёт завершения («либо все завершатся, либо все отменятся»). Изоморф границам блоков структурного программирования: goto (голые потоки/голые Future) заменён блоком (группа-область) — утечка параллельной операции (забытый ожиданием Future) в структурном виде невозможна: выход из области и есть точка сведения. Родитель-потомок вкладывает эту структуру: группа обработки запроса — дочерняя группе соединения, группа соединения — дочерняя группе службы; остановка службы отменяет группу службы — всё дерево откликается.
60.2概念
Пятёрка ядра группы задач
Два способа регистрации: Add сопровождает готовый Future (группа держит ссылку до финала — балансировка ссылок на группе); Start сперва резервирует слот, потом вызывает процедуру запуска (процедура возвращает Future; при отказе незарегистрированного остатка нет — атомарность регистрации и запуска). Две ступени закрытия: Close — естественный финал (остаток добегает), Cancel — кооперативная отмена (большая кнопка дерева отмены главы 54: запрос текущим пунктам и подгруппам). Четыре формы сведение: Wait (бесконечно), For/Until (со сроком), WaitCancel (отменяемое вызывающим — транзитная форма вложенных ожиданий, xrtTaskGroupWaitForCancel).
Статистика финалов: полный перечень разом
| Поле | Семантика |
|---|---|
Succeeded | число задач Resolved |
Failed | число задач FAILED |
Cancelled | число задач CANCELLED |
Closed | число задач CLOSED |
Rejected | число подач, отклонённых после прекращения приёма |
Active / Added / Completed | живые / накопленно / завершено |
Статистическое приземление «отмена ≠ отказа» главы 58: три финала считаются раздельно — отчётность (журнал главы 39) больше не требует ручной классификации. Rejected заслуживает внимания: попытки подачи после закрытия тоже считаются (улика перегрузки — готовое поле наблюдения главы 50).
Области родитель-потомок: дерево отмены структурно
xrtTaskGroupChild(父, 配置) (родитель, конфигурация) порождает подгруппу — Cancel родителя распространяет отмену на подгруппы и листовые Future (автоматическая форма дерева главы 54); финал листа подтверждает производящая сторона — после запроса отмены производитель (держатель Promise) выбирает Reject/Resolve/Cancel (образец scope проверяет parent completed = 1, cancelled = 1: один лист завершился нормально, другой подтвердил отмену — два листовых финала при одной родительской отмене). Wait подгруппы ждёт только подгруппу (локальное сведение); Wait родителя — всё дерево (глобальное) — гранулярность ожидания совпадает с гранулярностью области.
Done Future группы: асинхронное сведение
xrtTaskGroupFuture возвращает Done Future группы (успех, когда группа закрыта и активные обнулились) — само сведение асинхронно: завершение группы может быть звеном цепочки продолжений (глава 58), может стоять под Watch (обратные вызовы главы 58), может входить в другую группу (группы в группах — Done Future через Add в верхнюю группу). Точка ожидания из блокирующего вызова превращается в компонуемое значение первого класса — место слияния структурного параллелизма с системой Future.
Группа задач с корутинами и пулом
Семейство образцов task разворачивает три формы сотрудничества. Группа + пул (task_group_pool): задачи исполняются через пул, Future вешаются в группу — пул ведает исполнением, группа сведение; группа + корутина (task_coroutine / task_group_coroutine): процедура корутины возвращает Future в группу — многошаговая асинхронность внутри корутины целиком как один пункт группы; группа + Promise (главный пример task_group): рукотворный Promise тоже вешается — группе безразлично, откуда Future. Три источника, одно сведение — группа это слой сведения финалов, ортогональный слою исполнения (пул/планировщик).
60.3示例
Полная программа: одна точка ожидания на группу
Из образца репозитория examples/concurrency/task_group/main.c — два участника, одно сведение, статистика финалов:
#include <stdio.h> #include <xrt.h> */ /* 用任务组统一等待多个独立 Future,并读取累计终态。 */ int main(void) { xtaskgroup* pGroup = xrtTaskGroupCreate(NULL); xfuture* pFirst; xfuture* pSecond; xpromise* pFirstPromise = xrtPromiseCreate(&pFirst, NULL); xpromise* pSecondPromise = xrtPromiseCreate(&pSecond, NULL); xtaskgroupstats tStats; if ( (pGroup == NULL) || (pFirstPromise == NULL) || (pSecondPromise == NULL) ) { return 1; } if ( !xrtTaskGroupAdd(pGroup, pFirst) || !xrtTaskGroupAdd(pGroup, pSecond) ) { return 2; } (void)xrtPromiseResolve(pFirstPromise, NULL); (void)xrtPromiseResolve(pSecondPromise, NULL); if ( xrtTaskGroupWait(pGroup) != XWAIT_OK ) { return 3; } (void)xrtTaskGroupGet(pGroup, &tStats); printf( "completed: %llu, succeeded: %llu\n", (unsigned long long)tStats.Completed, (unsigned long long)tStats.Succeeded ); xrtPromiseDestroy(pFirstPromise); xrtPromiseDestroy(pSecondPromise); xrtFutureDestroy(pFirst); xrtFutureDestroy(pSecond); xrtTaskGroupDestroy(pGroup); return 0; }
Что только что произошло. (1) xrtTaskGroupCreate(NULL) строит группу (NULL — умолчания); созданы две пары Promise/Future (рукотворная производящая сторона — имитация асинхронных операций). (2) Оба Future Add-нуты — ссылками завладела группа (финалы Future опубликует сторона Promise). (3) Стороны Promise публикуют два значения — оба Future в финале Resolved. (4) xrtTaskGroupWait одним ожиданием — две задачи — вместо цикла Wait-ов; xtaskgroupstats берёт всё разом: completed: 2, succeeded: 2. Это минимальная эмпирика «группа — одна точка ожидания, финалы — одним заходом»: сорок строк ручного кода из введения исчезли целиком.
Полная программа: отмена в областях родитель-потомок
Из examples/concurrency/task_group_scope/main.c — структурная форма дерева отмены:
#include <stdio.h> #include <xrt.h> */ */ int main(void) { int iResult = 1; xtaskgroup* pParent = NULL; xtaskgroup* pChild = NULL; xfuture* pLeaf = NULL; xfuture* pDone = NULL; xpromise* pLeafPromise = NULL; xcancel* pLeafCancel = NULL; xtaskgroupstats tStats; /* 子组作为父组中的一项,叶 Future 再由子组跟踪。 */ pParent = xrtTaskGroupCreate(NULL); if ( pParent == NULL ) { goto cleanup; } pChild = xrtTaskGroupChild(pParent, NULL); pLeafPromise = xrtPromiseCreate(&pLeaf, NULL); if ( (pChild == NULL) || (pLeafPromise == NULL) || !xrtTaskGroupAdd(pChild, pLeaf) ) { goto cleanup; } pDone = xrtTaskGroupFuture(pParent); pLeafCancel = xrtFutureCancelToken(pLeaf); if ( (pDone == NULL) || (pLeafCancel == NULL) ) { goto cleanup; } /* 父级只发出协作取消请求,叶生产端负责确认自己的终态。 */ if ( !xrtTaskGroupCancel(pParent) || !xrtCancelRequested(pLeafCancel) || !xrtPromiseCancel(pLeafPromise) || (xrtFutureWait(pDone) != XWAIT_OK) ) { goto cleanup; } if ( (xrtFutureState(pLeaf) != XFUTURE_CANCELLED) || !xrtTaskGroupGet(pParent, &tStats) ) { goto cleanup; } printf( "parent completed = %llu, cancelled = %llu\n", (unsigned long long)tStats.Completed, (unsigned long long)tStats.Cancelled ); iResult = 0; cleanup: /* 子组和父组允许在仍有活动监听时延迟完成内部回收。 */ xrtCancelDestroy(pLeafCancel); xrtFutureDestroy(pDone); xrtPromiseDestroy(pLeafPromise); xrtFutureDestroy(pLeaf); xrtTaskGroupDestroy(pChild); xrtTaskGroupDestroy(pParent); return iResult; }
Что только что произошло. (1) Родительская группа построена, подгруппа порождена xrtTaskGroupChild, листовые Future висят в подгруппе — трёхуровневое дерево областей. (2) Cancel родителя — запрос отмены распространяется по дереву на подгруппу и все листья (автоматическая версия дерева главы 54: токены каждому листу вручную не пересылаются). (3) Самостоятельное подтверждение финала производящей стороной листа: один лист успел завершиться до отмены (completed=1), другой, ответив на отмену, подтвердил Cancelled (cancelled=1) — запрос уходит единообразно, финалы подтверждаются каждый сам (полная семантика кооперативной отмены: запрос ≠ результат, право решающего финал — у производящей стороны). (4) После Wait родителя статистика несёт смешанные финалы — вывод completed = 1, cancelled = 1 и есть машинное свидетельство этой дисциплины. Образец task_tour — обход полного интерфейса (submit/группа/пул/отмена/сроки — по группе ok).
60.4契约
- Атомарность регистрации: Start резервирует слот прежде запуска — отказ не оставляет незарегистрированных пунктов; после Add ссылками ведает группа.
- Две ступени закрытия: Close — естественный финал (остаток добегает) / Cancel — кооперативная отмена (древовидное распространение) — по семантике остановки.
- Самостоятельность финалов: запрос отмены уходит единообразно, финал подтверждает производящая сторона — троичная статистика отменено ≠ отказано ≠ закрыто.
- Гранулярность ожидания: подгруппа ждёт подгруппу, родитель — всё дерево — ожидание совпадает с областями; четыре формы семейства Wait (бесконечно/For-Until со сроком/UntilCancel отменяемо).
- Done Future: сведение асинхронно — событие завершения группы вешается в верхнюю группу, в продолжения, под Watch.
- Ортогональность источников: Future из пула/корутины/Promise — любые — группа слой сведение, слой исполнения компонуется свободно.
От примеров к инженерии: три хозяина групп задач
Обработка запросов (самое частое): подгруппа на каждый запрос — параллельные операции запроса (кеш/база/нижний поток) все в подгруппе, ответ или тайм-аут — сведение или отмена подгруппы, разрушение подгруппы — конец запроса: жизненный цикл запроса строго совпадает с областью (полная форма упражнения-вызова). Оркестровка службы: служебная родительская группа держит все группы соединений/запросов — сигнал остановки главы 49 зажигает Cancel родителя, всё дерево откликается, Wait родителя — безопасный выход: структурная реализация грациозной остановки (контраст с ручной редакцией «пути остановки» главы 54 — группа задач её регламентирует). Пакетные задания: пачка задач — одна группа: Wait группы — завершение пачки, stats — отчёт пачки (успех/отказ/отмена по графам), политика повторов при частичных отказах ведётся списком Failed. Общая форма трёх хозяев: граница области = граница жизненного цикла = точка сведение — в этом инженерный смысл «структурности».
Структурный параллелизм: прошлое и настоящее
Структурный параллелизм не изобретение — формализация старых уроков. Боль без структуры: голые потоки/голые Future как goto — поток управления «выпрыгнул и не вернулся» (забыли ждать, не отменить, утечка ничья); дискуссии «ада обратных вызовов» около 2016 года были по сути катастрофой читаемости неструктурного параллелизма. Линия формализации: концепция nurseries (Nathaniel Smith, автор Trio, 2017) впервые систематизировала «внутри области — обязательное сведение»; structured concurrency в Kotlin (2019) принесла её в мейнстрим; TaskGroup в Swift (2022) дала образец формы API. Группа задач XRT — C-реализация этой линии: четыре проектных пункта — порождение Child, две ступени Close/Cancel, троица финалов, Future группы — находят соответствия по всей линии. Ценность знания линии: структурность — не «ещё один стиль API», а формальная гарантия правильности параллелизма (гарантия сведение на выходе из области превращает «утечку параллельной операции» из «избегаемого дисциплиной» в «структурно невозможное»).
Слияние с наблюдением главы 50: статистика финалов и есть наблюдение
Структура stats группы — пост конвейера наблюдения главы 50 в конечной точке параллелизма. Ритм отчётности: пакетные группы отчитываются разом после Wait (структурные поля журнала главы 38 — Succeeded/Failed/Cancelled тремя графами в JSON); служебные родительские группы семплируют Active периодически (кривая живых — редакция метрик здоровья планировщика главы 56 для задач). Триаж тревог: Failed>0 — тревога; всплеск Cancelled — искать верхний поток (кто отменяет); Rejected>0 — разбирать перегрузку (прекращение приёма группы — противодавление или остановка?). Сверка: статистики подгрупп запросов сумм-согласованы с накоплением родителя соединения (самосогласованность вложенной статистики — пункт приёмки упражнения-вызова). Статистика финалов из «ручного пересчёта» превращается в «структурно порождаемое поле наблюдения» — линия отмены тома 6 находит последнюю опору в конвейере главы 50.
Типичные ошибки
Симптом: после разрушения группы задачи всё ещё исполняются — висячее исполнение, потерянная статистика, изредка крах (ресурсы группы освобождены, а финалам задач нужно писать в статистику).
Причина: выход из области — точка сведение: перед Destroy обязателен Wait (или WaitCancel); это «фигурная скобка» структурного параллелизма.
xtaskgroup* Group = xrtTaskGroupCreate(NULL); xrtTaskGroupStart(Group, launchQuery, NULL); /* асинхронная задача бежит */ xrtTaskGroupDestroy(Group); /* разрушено напрямую - задача повисла */
xtaskgroup* Group = xrtTaskGroupCreate(NULL); xrtTaskGroupStart(Group, launchQuery, NULL); if ( xrtTaskGroupWait(Group) != XWAIT_OK ) { /* выход = сведение (фигурная скобка структуры) */ xrtTaskGroupCancel(Group); /* или Cancel и ожидание со сроком */ } xrtTaskGroupDestroy(Group); /* разрушение после сведение */
Симптом: штатная остановка (активная отмена работ в полёте) репортится как авария — ложные тревоги мониторинга, шум; пути обработки отката и отказа перепутаны.
Причина: троица финалов (Succeeded/Failed/Cancelled) не вошла в решения — Cancelled после Cancel это ожидаемый исход, а не ошибка.
if ( Stats.Failed + Stats.Cancelled > 0 ) { Alert("tasks failed"); /* и отмена тревожит - иллюзия «остановка = авария» */ }
if ( Stats.Failed > 0 ) { Alert("tasks failed"); } /* настоящие отказы - тревога */ if ( Stats.Cancelled > 0 ) { LogInfo("tasks cancelled"); } /* отмены - в журнал */ /* остановка релиза и остановка аварии рапортуются раздельно (статистическая сторона дисциплины главы 54) */
Практические задания
Группа из трёх задач: одна Resolve, одна Reject, одна после Cancel подтверждает Cancelled — после Wait печатается полная строка финалов stats, сверка по пунктам.
Родитель → две подгруппы (по три задачи) — Cancel родителя и наблюдение распространения; затем контраст: Cancel только одной подгруппы (родитель и брат не замечают) — оба направления по разу, вывод статистики каждого уровня.
Полная группа запроса: запрос прибыл → подгруппа создана → три конкурентных пути (кеш/база/гонка тайм-аута) в подгруппе → сведение подгруппы (семантика First вручную) → ответ → разрушение подгруппы. Родитель (уровень соединения) держит все подгруппы запросов; при разрыве соединения родитель Cancel. Критерии приёмки: жизненный цикл подгруппы запроса строго совпадает с запросом (ноль утечек — статистика главы 6); при разрыве все запросы в полёте завершаются (финалы классифицированы верно); трёхуровневая статистика сходится.
Итоги
| Тема | Кратко |
|---|---|
| Пятёрка ядра | Create строит / Add-Start регистрируют / Close-Cancel две ступени закрытия / семейство Wait сводит / stats считает |
| Перечень финалов | Succeeded/Failed/Cancelled/Closed/Rejected раздельно - отмена ≠ отказ |
| Области родитель-потомок | Child порождает подгруппы; Cancel родителя распространяется по дереву; финалы листьев подтверждаются производителями самостоятельно |
| Done Future | GroupFuture возвращает Future завершения - сведение асинхронно, вешается в верхние группы |
| Атомарная регистрация | Start резервирует слот прежде запуска - отказ не оставляет незарегистрированных пунктов |
| Выход = сведение | Wait/Cancel прежде Destroy - «фигурная скобка» структуры |
| Ортогональность источников | Future из пула/корутин/Promise - любые; слой сведение расцеплен со слоем исполнения |
📖 Справочники API в этой главе:task (50 записей)future (85 записей)cancel (18 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки