¿Por qué las fibras no pueden utilizar múltiples procesadores?

8

Parece que la distinción entre fibras e hilos es que las fibras se programan de forma cooperativa, mientras que los hilos se programan de forma preventiva. El punto del planificador parece ser una forma de hacer que un recurso de procesador en serie actúe de manera paralela, "compartiendo el tiempo" de la CPU. Sin embargo, en un procesador de doble núcleo con cada núcleo ejecutando su propio hilo, supongo que no hay necesidad de pausar la ejecución de un hilo para que el otro continúe porque no están "compartiendo tiempo" con un solo procesador.

Entonces, si la diferencia entre hilos y fibras es la forma en que el programador los interrumpe, y la interrupción no es necesaria cuando se ejecuta en núcleos físicamente separados, ¿por qué las fibras no pueden aprovechar los núcleos de múltiples procesadores cuando los hilos sí pueden?

Fuentes de confusión:

.. principalmente wikipedia

  1. http://en.wikipedia.org/wiki/Fiber_%28computer_science%29

    Una desventaja es que las fibras no pueden utilizar máquinas multiprocesador sin utilizar también hilos preventivos

  2. http://en.wikipedia.org/wiki/Computer_multitasking#Multithreading

    ... [las fibras] tienden a perder algunos o todos los beneficios de los hilos en máquinas con múltiples procesadores.

James M. Lay
fuente

Respuestas:

9

La distinción principal, como señala en su pregunta, es si el programador alguna vez se adelantará a un hilo. La forma en que un programador piensa en compartir estructuras de datos o en sincronizar entre "hilos" es muy diferente en sistemas preventivos y cooperativos.

En un sistema cooperativo (que va por muchos nombres, de cooperación multi-tarea , nonpreemptive multitarea , las discusiones a nivel de usuario , hilos verdes , y las fibras son cinco de las más comunes en la actualidad) que el programador está garantizado que su código se ejecutará de forma atómica , siempre y cuando No hacen llamadas ni llamadas al sistema yield(). Esto hace que sea particularmente fácil tratar con estructuras de datos compartidas entre múltiples fibras. A menos que necesite realizar una llamada al sistema como parte de una sección crítica, no es necesario marcar las secciones críticas (con mutex locky unlockllamadas, por ejemplo). Entonces en código como:

x = x + y
y = 2 * x

el programador no necesita preocuparse de que alguna otra fibra pueda estar trabajando con las variables xy yal mismo tiempo. xy yse actualizarán juntos atómicamente desde la perspectiva de todas las otras fibras. Del mismo modo, todas las fibras podrían compartir una estructura más complicada, como un árbol y una llamada similar tree.insert(key, value)no necesitaría estar protegida por ningún mutex o sección crítica.

Por el contrario, en un sistema de subprocesamiento múltiple preventivo, como con subprocesos verdaderamente paralelos / multinúcleo, todas las posibles intercalaciones de instrucciones entre subprocesos son posibles a menos que haya secciones críticas explícitas. Una interrupción y una preferencia podrían convertirse entre dos instrucciones. En el ejemplo anterior:

 thread 0                thread 1
                         < thread 1 could read or modify x or y at this point
 read x
                         < thread 1 could read or modify x or y at this point
 read y
                         < thread 1 could read or modify x or y at this point
 add x and y
                         < thread 1 could read or modify x or y at this point
 write the result back into x
                         < thread 1 could read or modify x or y at this point
 read x
                         < thread 1 could read or modify x or y at this point
 multiply by 2
                         < thread 1 could read or modify x or y at this point
 write the result back into y
                         < thread 1 could read or modify x or y at this point

Entonces, para ser correcto en un sistema preventivo, o en un sistema con hilos verdaderamente paralelos, debe rodear cada sección crítica con algún tipo de sincronización, como un mutex lockal principio y un mutex unlockal final.

Por lo tanto, las fibras son más similares a las bibliotecas de E / S asíncronas que a los hilos preventivos o hilos verdaderamente paralelos. Se invoca el planificador de fibra y puede cambiar fibras durante operaciones de E / S de latencia larga. Esto puede brindar el beneficio de múltiples operaciones simultáneas de E / S sin requerir operaciones de sincronización alrededor de secciones críticas. Por lo tanto, el uso de fibras puede, quizás, tener menos complejidad de programación que los hilos preventivos o verdaderamente paralelos, pero la falta de sincronización alrededor de las secciones críticas conduciría a resultados desastrosos si intentara ejecutar las fibras de manera realmente simultánea o preventiva.

Lógica Errante
fuente
Creo que debería mencionarse probablemente 1. sistemas híbridos donde el sistema de subprocesos a nivel de usuario se encarga de distribuir (muchos) subprocesos a nivel de usuario a través de (pocos) núcleos de CPU y 2. el hecho de que cuando se programa en "metal desnudo" , es posible obtener multiprocesamiento sin preferencia.
dfeuer
1
@dfeuer No creo que la pregunta sea sobre todas las diferentes formas posibles de aprovechar el multiprocesamiento. La pregunta que leí es "¿por qué las fibras (también conocidas como tareas no preventivas) no pueden tratarse como hilos preventivos?" Si está asumiendo un paralelismo real, entonces tiene que sincronizar correctamente, para que ya no tenga "fibras".
Wandering Logic
1
Hermosa respuesta Las fibras no pueden garantizar la seguridad porque el programa supondría que tiene acceso exclusivo a recursos compartidos hasta que especifique un punto de interrupción, donde los hilos suponen que se puede hacer un acceso / mutación en cualquier punto; obviamente, la suposición más segura cuando múltiples nodos verdaderamente paralelos interactúan con los mismos datos.
James M. Lay
6

La respuesta es en realidad que podrían, pero hay un deseo de no hacerlo.

Las fibras se usan porque le permiten controlar cómo se produce la programación. En consecuencia, es mucho más simple diseñar algunos algoritmos usando fibras porque el programador ha dicho en qué fibra se está ejecutando en cualquier momento. Sin embargo, si desea que dos fibras se ejecuten en dos núcleos diferentes al mismo tiempo, debe programarlas manualmente para que lo hagan.

Los subprocesos dan el control de qué código se está ejecutando en el sistema operativo. A cambio, el sistema operativo se encarga de muchas tareas feas para usted. Algunos algoritmos se vuelven más difíciles, porque el programador tiene menos voz en qué código se ejecuta en un momento dado, por lo que pueden surgir más casos inesperados. Se agregan herramientas como mutexes y semáforos a un sistema operativo para dar al programador el control suficiente para hacer que los hilos sean útiles y vencer parte de la incertidumbre, sin atascar al programador.

Esto lleva a algo que es aún más importante que cooperativo frente a preventivo: las fibras son controladas por el programador, mientras que los hilos son controlados por el sistema operativo.

No querrás tener que generar una fibra en otro procesador. Los comandos de nivel de ensamblaje para hacerlo son atrozmente complicados y, a menudo, son específicos del procesador. No desea tener que escribir 15 versiones diferentes de su código para manejar estos procesadores, por lo que recurre al sistema operativo. El trabajo del sistema operativo es abstraer estas diferencias. El resultado es "hilos".

Las fibras se ejecutan sobre hilos. No corren solos. En consecuencia, si desea ejecutar dos fibras en diferentes núcleos, simplemente puede generar dos hilos y ejecutar una fibra en cada uno de ellos. En muchas implementaciones de fibras, puede hacer esto fácilmente. El soporte multinúcleo no proviene de las fibras, sino de los hilos.

Se vuelve fácil mostrar que, a menos que desee escribir su propio código específico de procesador, no hay nada que pueda hacer al asignar fibras a múltiples núcleos que no podría hacer al crear hilos y asignar fibras a cada uno. Una de mis reglas favoritas para el diseño de API es "Una API no se realiza cuando ha terminado de agregarle todo, sino más bien cuando ya no puede encontrar nada más que eliminar". Dado que el multinúcleo se maneja perfectamente al alojar fibras en subprocesos, no hay razón para complicar la API de fibra al agregar multinúcleo a ese nivel.

Cort Ammon
fuente