multithreading promise perl6

multithreading - ¿Cuántas promesas puede mantener Perl 6?



promise perl6 (2)

Es un título un poco flibante, pero al jugar con Promises quería ver hasta dónde podía estirar la idea. En este programa, lo hago para poder especificar cuántas promesas quiero hacer.

  • El valor predeterminado en el programador de hilos es 16 hilos ( rakudo / ThreadPoolScheduler.pm )
  • Si especifico más que ese número, el programa se bloquea pero no aparece una advertencia (por ejemplo, "Demasiados hilos").
  • Si configuro RAKUDO_MAX_THREADS, puedo detener la ejecución del programa, pero finalmente hay demasiada competencia de subprocesos para ejecutar.

Tengo dos preguntas, de verdad.

  • ¿Cómo sabría un programa cuántos hilos más puede hacer? Eso es un poco más que el número de promesas, por lo que eso vale.

  • ¿Cómo sabría cuántos hilos debería permitir, incluso si puedo hacer más?

Este es Rakudo 2017.01 en mi diminuta Macbook Air con 4 núcleos:

my $threads = @*ARGS[0] // %*ENV<RAKUDO_MAX_THREADS> // 1; put "There are $threads threads"; my $channel = Channel.new; # start some promises my @promises; for 1 .. $threads { @promises.push: start { react { whenever $channel -> $i { say "Thread {$*THREAD.id} got $i"; } } } } put "Done making threads"; for ^100 { $channel.send( $_ ) } put "Done sending"; $channel.close; await |@promises; put "Done!";


use v6.d.PREVIEW; rápida, agregue el use v6.d.PREVIEW; en la primera linea
Esto soluciona una serie de problemas de agotamiento de hilos.

Agregué algunos otros cambios como $*SCHEDULER.max_threads y agregué el "id" de Promesa para que sea fácil ver que el id. Del Subproceso no se correlaciona necesariamente con una Promesa dada.

#! /usr/bin/env perl6 use v6.d.PREVIEW; # <-- my $threads = @*ARGS[0] // $*SCHEDULER.max_threads; put "There are $threads threads"; my $channel = Channel.new; # start some promises my @promises; for 1 .. $threads { @promises.push: start { react { whenever $channel -> $i { say "Thread $*THREAD.id() ($_) got $i"; } } } } put "Done making threads"; for ^100 { $channel.send( $_ ) } put "Done sending"; $channel.close; await @promises; put "Done!";

There are 16 threads Done making threads Thread 4 (14) got 0 Thread 4 (14) got 1 Thread 8 (8) got 3 Thread 10 (6) got 4 Thread 6 (1) got 5 Thread 16 (5) got 2 Thread 3 (16) got 7 Thread 7 (8) got 8 Thread 7 (9) got 9 Thread 5 (3) got 6 Thread 3 (6) got 10 Thread 11 (2) got 11 Thread 14 (5) got 12 Thread 4 (16) got 13 Thread 16 (15) got 14 # << Thread 13 (11) got 15 Thread 4 (15) got 16 # << Thread 4 (15) got 17 # << Thread 4 (15) got 18 # << Thread 11 (15) got 19 # << Thread 13 (15) got 20 # << Thread 3 (15) got 21 # << Thread 9 (13) got 22 Thread 18 (15) got 23 # << Thread 18 (15) got 24 # << Thread 8 (13) got 25 Thread 7 (15) got 26 # << Thread 3 (15) got 27 # << Thread 7 (15) got 28 # << Thread 8 (15) got 29 # << Thread 13 (13) got 30 Thread 14 (13) got 31 Thread 8 (13) got 32 Thread 6 (13) got 33 Thread 9 (15) got 34 # << Thread 13 (15) got 35 # << Thread 9 (15) got 36 # << Thread 16 (15) got 37 # << Thread 3 (15) got 38 # << Thread 18 (13) got 39 Thread 3 (15) got 40 # << Thread 7 (14) got 41 Thread 12 (15) got 42 # << Thread 15 (15) got 43 # << Thread 4 (1) got 44 Thread 11 (1) got 45 Thread 7 (15) got 46 # << Thread 8 (15) got 47 # << Thread 7 (15) got 48 # << Thread 17 (15) got 49 # << Thread 10 (10) got 50 Thread 10 (15) got 51 # << Thread 11 (14) got 52 Thread 6 (8) got 53 Thread 5 (13) got 54 Thread 11 (15) got 55 # << Thread 11 (13) got 56 Thread 3 (13) got 57 Thread 7 (13) got 58 Thread 16 (16) got 59 Thread 5 (15) got 60 # << Thread 5 (15) got 61 # << Thread 6 (15) got 62 # << Thread 5 (15) got 63 # << Thread 5 (15) got 64 # << Thread 17 (11) got 65 Thread 15 (15) got 66 # << Thread 17 (15) got 67 # << Thread 11 (13) got 68 Thread 10 (15) got 69 # << Thread 3 (15) got 70 # << Thread 11 (15) got 71 # << Thread 6 (15) got 72 # << Thread 16 (13) got 73 Thread 6 (13) got 74 Thread 17 (15) got 75 # << Thread 4 (13) got 76 Thread 8 (13) got 77 Thread 12 (15) got 78 # << Thread 6 (11) got 79 Thread 3 (15) got 80 # << Thread 11 (13) got 81 Thread 7 (13) got 82 Thread 4 (15) got 83 # << Thread 7 (15) got 84 # << Thread 7 (15) got 85 # << Thread 10 (15) got 86 # << Thread 7 (15) got 87 # << Thread 12 (13) got 88 Thread 3 (13) got 89 Thread 18 (13) got 90 Thread 6 (13) got 91 Thread 18 (13) got 92 Thread 15 (15) got 93 # << Thread 16 (15) got 94 # << Thread 12 (15) got 95 # << Thread 17 (15) got 96 # << Thread 11 (13) got 97 Thread 15 (16) got 98 Thread 18 (7) got 99 Done sending Done!


En realidad, esto no se trata de Promise per se, sino más bien del programador del grupo de subprocesos. Una Promise sí misma es solo una construcción de sincronización. La construcción de start realmente hace dos cosas:

  1. ¡Garantiza $_ , $/ y $! nuevos $! dentro del bloque
  2. Llamadas Promise.start con ese bloque

Y Promise.start también hace dos cosas:

  1. Crea y devuelve una Promise
  2. Programa el código en el bloque que se ejecutará en el grupo de subprocesos, y organiza que la finalización exitosa conserve la Promise y una excepción rompa la Promise .

No solo es posible, sino que también es relativamente común, tener objetos Promise que no estén respaldados por código en el grupo de subprocesos. Promise.in , Promise.anyof y Promise.allof fábricas no programan nada inmediatamente, y hay todo tipo de usos de una Promise que implica hacer Promise.new y luego llamar a keep o break más adelante. Entonces puedo crear y await fácilmente en 1000 Promise s:

my @p = Promise.new xx 1000; start { sleep 1; .keep for @p }; await @p; say ''done'' # completes, no trouble

Del mismo modo, una Promise no es lo único que puede programar código en ThreadPoolScheduler . Las muchas cosas que devuelven el Supply (como intervalos, visualización de archivos, conectores asíncronos, procesos asíncronos) también programan sus devoluciones de llamadas allí. Es posible lanzar código allí, disparando y olvidando el estilo haciendo $*SCHEDULER.cue: { ... } (aunque a menudo te importa el resultado, o cualquier error, por lo que no es especialmente común).

El planificador de grupo de subprocesos Perl 6 actual tiene un límite superior configurable pero forzado, que tiene un valor predeterminado de 16 subprocesos. Si crea una situación en la que los 16 están ocupados pero no puede avanzar, y lo único que puede progresar es atascarse en la cola de trabajo, entonces se producirá un bloqueo. Esto no es exclusivo del grupo de subprocesos Perl 6; cualquier grupo limitado será vulnerable a esto (y cualquier grupo ilimitado será vulnerable al uso de todos los recursos y a la muerte del proceso :-)).

Como se mencionó en otra publicación, Perl 6.d hará await y react construcciones que no bloqueen; este siempre ha sido el plan, pero no había suficientes recursos de desarrollo para realizarlo a tiempo para Perl 6.c. El use v6.d.PREVIEW pragma proporciona acceso temprano a esta característica. (También, advertencia justa, es un trabajo en progreso). El resultado de esto es que await o react en un hilo propiedad del grupo de subprocesos detendrá la ejecución del código programado (para los curiosos, tomando una continuación) y y permita que el hilo continúe con más trabajo. La reanudación del código se programará cuando se complete lo esperado o se done bloque de react . Tenga en cuenta que esto significa que puede estar en una secuencia de sistema operativo diferente antes y después de await o react en 6.d. (La mayoría de los usuarios de Perl 6 no tendrán que preocuparse por esto. Es principalmente relevante para quienes escriben enlaces a bibliotecas C, o para cosas sobre sistemas. Y una buena vinculación de biblioteca C lo hará para que los usuarios de la vinculación no tengan importar.)

El próximo cambio 6.d no elimina la posibilidad de agotar el grupo de subprocesos, pero significará que un montón de maneras que puede hacer en 6.c ya no serán motivo de preocupación (de nota, escribir concursos recursivos / dividir cosas que await los resultados de las partes divididas, o tener miles de bloques reactivos activos lanzados con start react { ... } ).

De cara al futuro, el programador del grupo de subprocesos también se volverá más inteligente. Lo que sigue es especulación, aunque dado que es probable que sea el que implementa los cambios, es probablemente la mejor especulación que se ofrece. :-) El grupo de subprocesos comenzará a seguir el progreso realizado y lo usará para ajustar dinámicamente el tamaño de la agrupación. Esto incluirá notar que no se está progresando y, combinado con la observación de que las colas de trabajo contienen elementos, agregar hilos para tratar de resolver el interbloqueo, a costa de la sobrecarga de memoria de los subprocesos agregados. Actualmente, el grupo de subprocesos conservadoramente tiende a engendrar hasta su tamaño máximo de todos modos, incluso si esta no es una opción particularmente óptima; lo más probable es que se use algún tipo de algoritmo de alpinismo para intentar establecer un número óptimo. Una vez que eso sucede, el valor predeterminado de max_threads puede elevarse sustancialmente, de modo que se puedan completar más programas, a costa de un montón de gastos generales de memoria, pero la mayoría se ejecutará con solo un puñado de subprocesos.