Ориентация главы

Группа задач (xtaskgroup) — структурный параллелизм в полной форме: одно множество задач — одна точка ожидания (xrtTaskGroupWait — больше не ждать каждый Future отдельно), сводная статистика финалов разом (xtaskgroupstats — полный перечень успех/отказ/отмена/закрыто/отклонено), области родитель-потомок (xrtTaskGroupChild — структурная форма дерева отмены: отмена родительской группы = запрос всему поддереву; финал листа всё равно подтверждает производящая сторона). Токены главы 54, Future главы 58 и пул главы 59 сходятся здесь — глава собирает россыпные параллельные инструменты в программные единицы с границами жизненного цикла.

60.1引入

Код «ждать пачку» без групп задач: 20 Future в массив, Wait по одному или цикл со счётчиком, ручная статистика успехов и отказов, при отказе одной отменить остальные — опять ручная пересылка токенов; сорок строк шаблона, переписываемых на каждом параллельном сведении, и каждая пропущенная ветвь (отмена — это отказ?) — баг статистики.

Группа задач сворачивает сорок строк в одно понятие. Важнее структурность: у параллельных операций есть границы областей — группа создаётся на входе, все задачи внутри принадлежат ей, на выходе из области группа ждёт завершения («либо все завершатся, либо все отменятся»). Изоморф границам блоков структурного программирования: goto (голые потоки/голые Future) заменён блоком (группа-область) — утечка параллельной операции (забытый ожиданием Future) в структурном виде невозможна: выход из области и есть точка сведения. Родитель-потомок вкладывает эту структуру: группа обработки запроса — дочерняя группе соединения, группа соединения — дочерняя группе службы; остановка службы отменяет группу службы — всё дерево откликается.

60.2概念

Пятёрка ядра группы задач

1СозданиеxrtTaskGroupCreate(конфигурация) - группа и есть область
2РегистрацияAdd (готовый Future) / Start (запуск процедуры с возвратом Future) - сперва резерв слота, потом запуск
3ЗакрытиеClose (прекратить приём, естественный финал) / Cancel (прекратить приём + кооперативный запрос отмены)
4СведениеWait / For / Until / UntilCancel - закрыть и ждать
5Статистикаxtaskgroupstats - полный перечень финалов разом

Два способа регистрации: 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 — два участника, одно сведение, статистика финалов:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/concurrency/task_group/main.c -lws2_32 -liphlpapicompleted: 2, succeeded: 2

Что только что произошло. (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 — структурная форма дерева отмены:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/concurrency/task_group_scope/main.c -lws2_32 -liphlpapiparent completed = 1, cancelled = 1

Что только что произошло. (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.

Типичные ошибки

Ловушка 1: разрушение без Wait/финалов:Ловушка 1: разрушение без Wait/финалов

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

Причина: выход из области — точка сведение: перед 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);                     /* разрушение после сведение */
Ловушка 2: Cancelled обработано как Failed:Ловушка 2: Cancelled обработано как Failed

Симптом: штатная остановка (активная отмена работ в полёте) репортится как авария — ложные тревоги мониторинга, шум; пути обработки отката и отказа перепутаны.

Причина: троица финалов (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 FutureGroupFuture возвращает Future завершения - сведение асинхронно, вешается в верхние группы
Атомарная регистрацияStart резервирует слот прежде запуска - отказ не оставляет незарегистрированных пунктов
Выход = сведениеWait/Cancel прежде Destroy - «фигурная скобка» структуры
Ортогональность источниковFuture из пула/корутин/Promise - любые; слой сведение расцеплен со слоем исполнения

📖 Справочники API в этой главе:task (50 записей)future (85 записей)cancel (18 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки