Ruby Multihilo
Cada programa que se ejecuta en el sistema es un proceso. Cada proceso contiene uno o más hilos.
Un hilo es un flujo de control secuencial único dentro de un programa. Ejecutar varios hilos simultáneamente en un solo programa para realizar diferentes tareas se denomina multihilo.
En Ruby, podemos crear múltiples hilos mediante la clase Thread. Los hilos de Ruby son ligeros y pueden implementar código paralelo de manera eficiente.
Crear hilos de Ruby
Para iniciar un nuevo hilo, solo necesita llamar a Thread.new:
Ejemplo
El siguiente ejemplo muestra cómo usar múltiples hilos en un programa Ruby:
Ejemplo
Pruébalo »
El resultado de ejecutar el código anterior es:
Started At Wed May 14 08:21:54 -0700 2014 func1 at: Wed May 14 08:21:54 -0700 2014 func2 at: Wed May 14 08:21:54 -0700 2014 func2 at: Wed May 14 08:21:55 -0700 2014 func1 at: Wed May 14 08:21:56 -0700 2014 func2 at: Wed May 14 08:21:56 -0700 2014 func1 at: Wed May 14 08:21:58 -0700 2014 End at Wed May 14 08:22:00 -0700 2014
Ciclo de vida de un hilo
1. Para crear un hilo se puede usar Thread.new, y también se pueden usar Thread.start o Thread.fork con la misma sintaxis para crear hilos.
2. Después de crear un hilo, no es necesario iniciarlo; el hilo se ejecutará automáticamente.
3. La clase Thread define algunos métodos para controlar los hilos. El hilo ejecuta el bloque de código dentro de Thread.new.
4. La última sentencia del bloque de código del hilo es el valor del hilo, que se puede obtener mediante los métodos del hilo. Si el hilo ha terminado de ejecutarse, devuelve el valor del hilo; de lo contrario, no devuelve un valor hasta que el hilo haya terminado.
5. El método Thread.current devuelve un objeto que representa el hilo actual. El método Thread.main devuelve el hilo principal.
6. Use el método Thread.join para ejecutar el hilo; este método suspende el hilo principal hasta que el hilo actual haya terminado.
Estado de los hilos
Los hilos tienen 5 estados:
| Estado del hilo | Valor de retorno |
|---|---|
| Ejecutable | run |
| Dormido | Sleeping |
| Salida | aborting |
| Terminación normal | false |
| Terminación anormal | nil |
Hilos y excepciones
Cuando un hilo lanza una excepción y no es capturada por rescue, el hilo normalmente se termina sin advertencia. Sin embargo, si otros hilos están esperando a ese hilo mediante Thread#join, los hilos en espera también recibirán la misma excepción.
Usando los siguientes 3 métodos, puede hacer que el intérprete se interrumpa cuando un hilo termine debido a una excepción.
- Especificar al iniciar el script-dla opción, y ejecutar en modo de depuración.
- usar
Thread.abort_on_exceptionEstablecer la bandera. - Usar
Thread#abort_on_exceptionEstablecer la bandera en el hilo especificado.
Después de usar uno de los 3 métodos anteriores, todo el intérprete se interrumpirá.
Control de sincronización de hilos
En Ruby, se proporcionan tres formas de implementar la sincronización, que son:
1. Sincronización de hilos mediante la clase Mutex
2. Sincronización de hilos mediante la clase Queue para la transferencia supervisada de datos
3. Control de sincronización mediante ConditionVariable
Sincronización de hilos mediante la clase Mutex
El control de sincronización de hilos se implementa mediante la clase Mutex. Si varios hilos necesitan una variable de programa al mismo tiempo, se puede bloquear esa variable con lock. El código es el siguiente:
Ejemplo
Pruébalo »
El resultado de ejecutar el código anterior es:
Synchronize Thread you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets
Además de usar lock para bloquear la variable, también puede usar try_lock, y también puede usar Mutex.synchronize para sincronizar el acceso a una variable.
Sincronización de hilos mediante la clase Queue para la transferencia supervisada de datos
La clase Queue representa una cola compatible con hilos y puede sincronizar el acceso al final de la cola. Diferentes hilos pueden usar la misma cola sin preocuparse de si los datos en la cola se sincronizan. Además, la clase SizedQueue puede limitar la longitud de la cola.
La clase SizedQueue puede ayudarnos a desarrollar aplicaciones de sincronización de hilos de manera muy conveniente, porque mientras se agregue a esta cola, no hay que preocuparse por la sincronización de hilos.
El clásico problema del productor-consumidor:
Ejemplo
Pruébalo »
La salida del programa:
SizedQuee Test 0 produced 1 produced consumed 0 2 produced consumed 1 consumed 2 3 produced consumed 34 produced consumed 4 5 produced consumed 5 6 produced consumed 6 7 produced consumed 7 8 produced 9 produced consumed 8 consumed 9
Variables de hilo
Los hilos pueden tener sus propias variables privadas. Las variables privadas del hilo se escriben en el hilo cuando se crea. Se pueden usar dentro del ámbito del hilo, pero no se pueden compartir fuera del hilo.
Pero a veces, ¿qué hacer si las variables locales de un hilo necesitan ser accedidas por otros hilos o por el hilo principal? Ruby proporciona la posibilidad de crear variables de hilo mediante nombres, tratando el hilo como una tabla hash. Los datos se escriben mediante []= y se leen mediante []. Veamos el siguiente código:
Ejemplo
El resultado de ejecutar el código anterior es:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
El hilo principal espera a que los hilos secundarios terminen de ejecutarse y luego genera cada valor por separado.
Prioridad de hilos
La prioridad de un hilo es el factor principal que afecta la programación de hilos. Otros factores incluyen el tiempo de ejecución de CPU ocupado, la programación por grupos de hilos, etc.
Puede usar el método Thread.priority para obtener la prioridad del hilo y el método Thread.priority= para ajustar la prioridad del hilo.
La prioridad predeterminada de un hilo es 0. Los hilos con mayor prioridad se ejecutan más rápido.
Un Thread puede acceder a todos los datos dentro de su propio ámbito, pero ¿qué hacer si necesita acceder a datos de otros hilos dentro de un hilo? La clase Thread proporciona métodos para el acceso mutuo a los datos de los hilos. Simplemente puede tratar un hilo como una tabla Hash: en cualquier hilo puede usar []= para escribir datos y [] para leerlos.
Se puede ver que, al tratar un hilo como una tabla Hash y usar los métodos [] y []=, logramos compartir datos entre hilos.
Exclusión mutua de hilos
Mutex (Mutual Exclusion = bloqueo mutuo) es un mecanismo utilizado en programación multihilo para evitar que dos hilos lean y escriban simultáneamente el mismo recurso compartido (por ejemplo, una variable global).
Ejemplo sin usar Mutex
Ejemplo
El resultado de ejecutar el ejemplo anterior es:
count1 : 9712487 count2 : 12501239 difference : 0
Ejemplo usando mutex
Ejemplo
El resultado de ejecutar el ejemplo anterior es:
count1 : 1336406 count2 : 1336406 difference : 0
Bloqueo mutuo
Cuando dos o más unidades de cálculo esperan mutuamente a que la otra deje de ejecutarse para obtener recursos del sistema, pero ninguna sale antes, esta situación se denomina bloqueo mutuo.
Por ejemplo, un proceso p1 ocupa el monitor y además necesita usar la impresora, pero la impresora está ocupada por el proceso p2, y p2 a su vez necesita usar el monitor, formando así un bloqueo mutuo.
Cuando usamos objetos Mutex, debemos prestar atención al bloqueo mutuo de hilos.
Ejemplo
El resultado del ejemplo anterior es:
A: I have critical section, but will wait for cv (Later, back at the ranch...) B: Now I am critical, but am done with cv B: I am still critical, finishing up A: I have critical section again! I rule!
Métodos de clase de Thread
La lista completa de métodos de la clase Thread es la siguiente:
| N.º | Descripción del método |
|---|---|
| 1 | Thread.abort_on_exception Si su valor es verdadero, en cuanto un hilo termine debido a una excepción, todo el intérprete se interrumpirá. Su valor predeterminado es falso, es decir, en circunstancias normales, si un hilo lanza una excepción y esta no es detectada por Thread#join u otros medios, el hilo se termina sin advertencia. |
| 2 | Thread.abort_on_exception= Si se establece entrue, en cuanto un hilo termine debido a una excepción, todo el intérprete se interrumpirá. Devuelve el nuevo estado. |
| 3 | Thread.critical Devuelve un valor booleano. |
| 4 | Thread.critical= Cuando su valor es true, no se producirá un cambio de hilo. Si el hilo actual se suspende (stop) o una señal (signal) interviene, su valor cambiará automáticamente a false. |
| 5 | Thread.current Devuelve el hilo que se está ejecutando actualmente (el hilo actual). |
| 6 | Thread.exit Termina la ejecución del hilo actual. Devuelve el hilo actual. Si el hilo actual es el único hilo, se usará exit(0) para terminar su ejecución. |
| 7 | Thread.fork { block } Genera un hilo igual que Thread.new. |
| 8 | Thread.kill( aThread ) Termina la ejecución del hilo. |
| 9 | Thread.list Devuelve un array de hilos vivos que están en estado de ejecución o suspendidos. |
| 10 | Thread.main Devuelve el hilo principal. |
| 11 | Thread.new( [ arg ]* ) {| args | block } Genera un hilo y comienza su ejecución. Los argumentos se pasan al bloque sin modificaciones. Esto permite pasar valores a las variables locales inherentes al hilo al mismo tiempo que se inicia el hilo. |
| 12 | Thread.pass Cede el derecho de ejecución a otros hilos. No cambia el estado del hilo en ejecución, sino que entrega el control a otros hilos ejecutables (programación explícita de hilos). |
| 13 | Thread.start( [ args ]* ) {| args | block } Genera un hilo y comienza su ejecución. Los argumentos se pasan al bloque sin modificaciones. Esto permite pasar valores a las variables locales inherentes al hilo al mismo tiempo que se inicia el hilo. |
| 14 | Thread.stop Suspende el hilo actual hasta que otro hilo lo despierte nuevamente usando el método run. |
Métodos de instancia de hilo
El siguiente ejemplo llama al método de instanciación de hilos join:
Ejemplo
La siguiente es la lista completa de métodos de instanciación:
| N.º | Descripción del método |
|---|---|
| 1 | thr[ name ] Recupera los datos inherentes correspondientes a name dentro del hilo. name puede ser una cadena o un símbolo. Si no hay datos correspondientes a name, devuelve nil. |
| 2 | thr[ name ] = Establece el valor de los datos inherentes correspondientes a name dentro del hilo. name puede ser una cadena o un símbolo. Si se establece a nil, se eliminarán los datos correspondientes dentro del hilo. |
| 3 | thr.abort_on_exception Devuelve un valor booleano. |
| 4 | thr.abort_on_exception= Si su valor es true, una vez que un hilo termina debido a una excepción, todo el intérprete será interrumpido. |
| 5 | thr.alive? Devuelve true si el hilo está "vivo". |
| 6 | thr.exit Termina la ejecución del hilo. Devuelve self. |
| 7 | thr.join Suspende el hilo actual hasta que el hilo self termine su ejecución. Si self termina debido a una excepción, se lanzará la misma excepción en el hilo actual. |
| 8 | thr.key? Devuelve true si los datos inherentes del hilo correspondientes a name ya han sido definidos. |
| 9 | thr.kill Similar aThread.exit 。 |
| 10 | thr.priority Devuelve la prioridad del hilo. El valor predeterminado de la prioridad es 0. Cuanto mayor sea el valor, mayor será la prioridad. |
| 11 | thr.priority= Establece la prioridad del hilo. También se puede establecer como un número negativo. |
| 12 | thr.raise( anException ) Fuerza el lanzamiento de una excepción dentro del hilo. |
| 13 | thr.run Reinicia un hilo que ha sido suspendido (stop). A diferencia de wakeup, realizará inmediatamente un cambio de hilo. Si se usa este método en un proceso muerto, se lanzará una excepción ThreadError. |
| 14 | thr.safe_level Devuelve el nivel de seguridad de self. El safe_level del hilo actual es el mismo que $SAFE. |
| 15 | thr.status Usa las cadenas "run", "sleep" o "aborting" para representar el estado de un hilo vivo. Si un hilo termina normalmente, devuelve false. Si termina debido a una excepción, devuelve nil. |
| 16 | thr.stop? Devuelve true si el hilo está en estado de terminación (dead) o suspendido (stop). |
| 17 | thr.value Espera hasta que el hilo self termine su ejecución (equivalente a join) y luego devuelve el valor de retorno del bloque del hilo. Si ocurre una excepción durante la ejecución del hilo, se volverá a lanzar dicha excepción. |
| 18 | thr.wakeup Cambia el estado de un hilo suspendido (stop) a estado ejecutable (run). Si se ejecuta este método en un hilo muerto, se lanzará una excepción ThreadError. |