Tema representativo de entrevista

Entrevista de C++26: ¿Cómo funcionan juntos los senders, receivers y operation states?

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Explica los roles de sender, receiver y operation state en C++26 std::execution. ¿Qué hacen connect y start, y cómo funcionan las finalizaciones de value, error y stopped?

Problema y contexto

Esta pregunta evalúa el modelo de objetos central en C++26 std::execution. Comienza con una operación asíncrona mínima y explica la descripción perezosa de un sender, el contrato de finalización de un receiver, el operation state producido por connect y el punto en el que start inicia el cómputo.

Qué evalúa el entrevistador

La distinción clave es entre la descripción perezosa de un sender y un operation state creado tras la conexión: connect construye el estado, mientras que start comienza el trabajo. Explica cómo set_value, set_error y set_stopped se mueven a través de la canalización (pipeline), si los cambios de scheduler afectan la afinidad de hilos y cómo la cancelación evita nuevos efectos secundarios.

Preguntas aclaratorias para hacer primero

Trabajo y contrapresión (backpressure)

Pregunta sobre el tamaño del lote, la concurrencia permitida, el orden de entrada y la política de reintentos para escrituras fallidas. Las respuestas determinan si se debe limitar la concurrencia de los senders o delimitar una cola.

Planificación y fuentes de detención (stop sources)

Pregunta cómo se exponen los schedulers de E/S y CPU, si las detenciones provienen de un tiempo de espera (timeout) o de una acción del usuario, y cómo se completa una llamada al sistema ya enviada. Una solicitud de detención no es una terminación forzada del hilo.

Recursos y semántica de confirmación (commit)

Aclara la propiedad de los identificadores de archivo (file handles), búferes y archivos temporales, si las escrituras son idempotentes y si los lotes parciales pueden revertirse. Esto define la limpieza después de set_error.

Marco de respuesta de 30 segundos

«Describo la lectura, el procesamiento (parse) y la escritura con adaptadores de sender, y luego uso adaptadores de scheduler para mover los contextos de ejecución. La canalización se mantiene perezosa: connect crea un operation state y start lo inicia. Cada etapa envía valores exitosos hacia adelante, errores a través de seterror y cancelaciones a través de setstopped; un único stop token llega a cada etapa. La ruta de detención comprueba antes de los efectos secundarios, mientras que los propietarios finalizan y cierran la E/S enviada y limpian los archivos temporales».

Pasos detallados de la solución

Paso 1: Definir los límites de valor y error

Define los tipos de entrada y salida para cada etapa y convierte las fallas recuperables en senders de error explícitos. No permitas que las excepciones crucen los límites del scheduler; permite que el receiver final registre el éxito, la falla o la detención.

Paso 2: Componer una canalización perezosa

Utiliza let_value, then o adaptadores equivalentes para conectar la lectura, el procesamiento y la escritura. La composición construye una descripción sin asignar hilos ni realizar E/S; el operation state es propietario de cualquier estado compartido que deba sobrevivir a una devolución de llamada (callback).

Paso 3: Conectar e iniciar

Llama a connect(sender, receiver) para obtener un operation state, mantenlo en un ámbito activo y luego llama a start. El receiver debe sobrevivir al operation state; las devoluciones de llamada asíncronas no pueden hacer referencia a objetos de pila destruidos.

Paso 4: Moverse entre contextos de ejecución

Una vez completada la E/S, usa un sender de scheduler para mover el procesamiento a un grupo de CPU y luego devuelve la escritura a un grupo de E/S delimitado. Registra la capacidad de la cola y la equidad (fairness); no coloques escrituras bloqueantes en un grupo general ilimitado.

Paso 5: Propagar la detención y la contrapresión

Pasa un stop token a cada etapa interrumpible. Después de una detención, no encoles nuevos lotes; cancela o finaliza una llamada al sistema en curso según su API. Cuando la cola está llena, un sender de limitación pausa el trabajo aguas arriba para acotar la memoria.

Paso 6: Manejar errores y efectos secundarios parciales

Escribe en un archivo temporal o registra una secuencia de lotes antes de confirmar atómicamente. set_error desencadena la limpieza aguas abajo y el cierre de identificadores. Los reintentos necesitan límites y una clave de idempotencia para que no puedan duplicar escrituras.

Paso 7: Verificar la concurrencia y el tiempo de vida

Prueba múltiples schedulers, condiciones de carrera de detención (stop races), fallas de procesamiento, escrituras cortas y destrucción temprana del receiver. Usa un analizador de hilos para detectar condiciones de carrera y mide el tiempo de cola, el rendimiento (throughput), la latencia de detención y los operation states no liberados.

Ejemplo de respuesta de alta calidad

El sender de lectura emite lotes, el sender de procesamiento se ejecuta en un scheduler de CPU y el sender de escritura se ejecuta en un scheduler de E/S delimitado. La canalización solo describe dependencias; connect crea el operation state y start lo lanza. Cada etapa maneja la finalización de valor, error y detención, y comparte un stop token. La detención bloquea nuevos lotes, permite que la E/S enviada se cierre de forma segura y utiliza archivos temporales junto con IDs de lote para reintentos idempotentes. Las pruebas cubren saltos de scheduler, contrapresión, condiciones de carrera de detención y tiempo de vida del receiver.

Errores comunes

  • Error: Asumir que un sender construido ya se está ejecutando. → Por qué: Un sender es perezoso (lazy). → Corrección: Especifica el límite de connect/start.
  • Error: Manejar excepciones pero no la finalización por detención (stopped). → Por qué: Stop es un canal de finalización independiente. → Corrección: Implementa tanto seterror como setstopped.
  • Error: Forzar la terminación de un hilo al cancelar. → Por qué: Puede poseer un identificador de archivo o una escritura parcial. → Corrección: Propaga un stop token y desenrolla de manera segura.
  • Error: Encolar lotes sin un límite. → Por qué: La falta de contrapresión puede agotar la memoria. → Corrección: Delimita la concurrencia, la capacidad de la cola y los reintentos.

Preguntas y respuestas de seguimiento

Pregunta de seguimiento 1: ¿Por qué no usar futures directamente?

Sender/receiver hace que la planificación, la cancelación y los tres canales de finalización sean componibles, y brinda control sobre el tiempo de vida en el momento de la conexión. Los futures generalmente necesitan convenciones adicionales para la propagación de detenciones y errores.

Pregunta de seguimiento 2: ¿Se puede destruir el sender después de start?

La descripción temporal del sender puede destruirse, pero el operation state, el receiver y los recursos capturados deben permanecer válidos hasta la finalización. Un objeto de tarea o un ámbito debe ser su propietario.

Pregunta de seguimiento 3: ¿set_stopped revierte todos los efectos secundarios?

No. Informa la finalización por detención; es posible que la E/S ya enviada no se revierta. Se necesitan archivos temporales, confirmaciones idempotentes o compensación para mantener la coherencia.

Pregunta de seguimiento 4: ¿Cómo demuestras que las escrituras no se duplican?

Asigna un ID de lote estable, verifica los IDs confirmados antes de escribir y permite que los reintentos escriban solo los IDs faltantes. Inyecta fallas, reinicios y condiciones de carrera de detención, y luego compara el registro de confirmaciones con el archivo final.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta