English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。
线程是程序中的一个单一顺序控制流程,在单个程序中同时运行多个线程以完成不同的工作,称为多线程。
在 Ruby 中,我们可以通过 Thread 类来创建多线程,Ruby 的线程是轻量级的,可以以高效的方式实现并行代码。
要启动一个新的线程,只需要调用 Thread.new 即可:
# 线程 #1 代码部分 Thread.new { # 线程 #2 执行代码 } # 线程 #1 执行代码
以下示例展示了如何在Ruby程序中使用多线程:
#!/usr/bin/ruby def func1 i=0 while i<=2 puts "func1 at: #{Time.now}" sleep(2) i=i+1 end end def func2 j=0 while j<=2 puts "func2 at: #{Time.now}" sleep(1) j=j+1 end end puts "Started At #{Time.now}" ()}1Thread.new{func1()} ()}2Thread.new{func2()} ()}1.join ()}2.join t
El resultado de la ejecución del código es:
puts "Finaliza el #{Time.now}" 14 08:21:54 -0700 2014 Inicio el mié may1 func 14 08:21:54 -0700 2014 Inicio el mié may2 func 14 08:21:54 -0700 2014 Inicio el mié may2 func 14 08:21:55 -0700 2014 Inicio el mié may1 func 14 08:21:56 -0700 2014 Inicio el mié may2 func 14 08:21:56 -0700 2014 Inicio el mié may1 func 14 08:21:58 -0700 2014 at: mié may 14 08:22Finaliza el mié may -0700 2014
1Vida del hilo
2El hilo se puede crear utilizando Thread.new, y también se puede usar el mismo sintaxis con Thread.start o Thread.fork para crear hilos.
3Después de crear un hilo, no es necesario iniciarlo, ya que se ejecutará automáticamente.
4El tipo Thread define algunos métodos para controlar los hilos. El hilo ejecuta el bloque de código en Thread.new.
5El último comando en el bloque de código del hilo es el valor del hilo, que se puede llamar a través de los métodos del hilo. Si el hilo se completa, se devuelve el valor del hilo; de lo contrario, no se devuelve un valor hasta que el hilo se complete.
6El método Thread.current devuelve el objeto que representa el hilo actual. El método Thread.main devuelve el hilo principal.
Se puede ejecutar el hilo a través del método Thread.Join, que bloqueará el hilo principal hasta que el hilo actual se complete.5Hay
estado del hilo | valor de retorno |
---|---|
ejecutable | correr |
dormir | Durmiente |
salida | abortando |
Finalización normal | false |
Finalización por excepción | nil |
Cuando un hilo lanza una excepción y no es capturada por rescue, el hilo generalmente se termina sin advertencia. Sin embargo, si otros hilos esperan a que este hilo finalice debido a Thread#join, también se lanzará la misma excepción a los hilos esperando.
begin t = Thread.new do Thread.pass # El hilo principal realmente espera a join raise "excepción no maneja" end t.join rescue p $! # => "excepción no maneja" end
Se utiliza la siguiente3Este método permite que el intérprete se interrumpa cuando algún hilo termina debido a una excepción.
Al especificar al iniciar el script-dopciones, y se ejecuta en modo depuración.
Se establece la marca utilizando Thread.abort_on_exception.
Se establece una marca en el hilo especificado utilizando Thread#abort_on_exception.
Al usar las siguientes3Este método interrumpirá todo el intérprete.
t = Thread.new { ... } t.abort_on_exception = true
En Ruby, se proporcionan tres formas de implementar la sincronización,分别是:
1. A través de la clase Mutex se realiza la sincronización de hilos
2. Regulación de la transferencia de datos mediante la clase Queue para la sincronización de hilos
3. Realizar el control de sincronización mediante ConditionVariable
Se realiza el control de sincronización de hilos mediante la clase Mutex, si se necesita un programa variable en varios hilos al mismo tiempo, se puede bloquear parte de esta variable con lock. El código es el siguiente:
#!/usr/bin/ruby require "thread" puts "Sincronizar Hilo" @num=200 @mutex=Mutex.new def comprarBoleto(num) @mutex.lock if @num>=num @num=@num-num puts "has comprado con éxito #{num} boletos" else puts "lo siento, no hay suficientes boletos" end @mutex.unlock end boleto1=Thread.new 10 do 10.times do |valor| boletoNum =15 comprarBoleto(boletoNum) sleep 0.01 end end boleto2=Thread.new 10 do 10.times do |valor| boletoNum =20 comprarBoleto(boletoNum) sleep 0.01 end end sleep 1 boleto1.join boleto2.join
El resultado de la ejecución del código es:
Sincronizar Hilo has comprado con éxito 15 boletos has comprado con éxito 20 boletos has comprado con éxito 15 boletos has comprado con éxito 20 boletos has comprado con éxito 15 boletos has comprado con éxito 20 boletos has comprado con éxito 15 boletos has comprado con éxito 20 boletos has comprado con éxito 15 boletos has comprado con éxito 20 boletos has comprado con éxito 15 boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos lo siento, no hay suficientes boletos
Además de usar lock para bloquear una variable, se puede usar try_lock para bloquear una variable, y también se puede usar Mutex.synchronize para sincronizar el acceso a una variable.
La clase Queue representa una cola que admite hilos y puede sincronizar el acceso al final de la cola. Diferentes hilos pueden usar la misma instancia de la clase, pero no tienen que preocuparse por si los datos en la cola están sincronizados, además, el uso de la clase SizedQueue permite limitar la longitud de la cola
La clase SizedQueue puede ayudarnos de manera muy conveniente a desarrollar aplicaciones de sincronización de hilos, ya que, una vez que se añade a esta cola, no hay que preocuparse por los problemas de sincronización de hilos.
Problema clásico de productor-consumidor:
#!/usr/bin/ruby require "thread" puts "Prueba de cola de tamaño" cola = Queue.new productor = Thread.new do 10.times do |i| sleep rand(i) # Hacer que el hilo duerma durante un tiempo cola << i puts "#{i} producido" end end consumer = Thread.new do 10.times do |i| value = queue.pop sleep rand(i/2) puts "consumed #{value}" end end consumer.join
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
Los hilos pueden tener variables privadas, las variables privadas de los hilos se escriben en el hilo al crearlo. Pueden ser utilizadas dentro del ámbito del hilo, pero no pueden ser compartidas fuera del ámbito del hilo.
Pero, ¿qué hacer cuando se necesitan variables locales de hilos accesibles por otros hilos o la línea principal? Ruby proporciona la creación de variables de hilo permitidas a través de nombres, al igual que ver a los hilos como tablas hash. Se pueden escribir datos usando []= y leer datos usando []. Vamos a ver el siguiente código:
#!/usr/bin/ruby count = 0 arr = [] 10.times do |i| arr[i] = Thread.new { sleep(rand(0)/10.0) Thread.current["mycount"] = count count += 1 } end arr.each {|t| t.join; print t["mycount"], " " } puts "count = #{count}"
El resultado de la ejecución del código anterior es:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
La línea principal espera que el hilo secundario complete su ejecución y luego salida cada valor. 。
El nivel de prioridad del hilo es un factor principal que afecta la programación de hilos. Otros factores incluyen la duración del tiempo de ejecución utilizado por el CPU, la programación de grupos de hilos, etc.
Se puede obtener el nivel de prioridad de un hilo utilizando el método Thread.priority y ajustar el nivel de prioridad utilizando Thread.priority=.
El nivel de prioridad de los hilos por defecto es 0. Los hilos con mayor prioridad se ejecutan más rápido.
Un hilo puede acceder a todos los datos de su ámbito, pero ¿qué hacer si se necesita acceder a los datos de otro hilo en un hilo determinado? La clase Thread proporciona métodos para que los hilos puedan acceder a datos entre sí, puedes simplemente ver a un hilo como una tabla hash, puedes escribir datos usando []= en cualquier hilo y leer datos usando [].
athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop } bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop } cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop } Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}" }
可以看到,把线程作为一个 Hash 表,使用 [] 和 []= 方法,我们实现了线程之间的数据共享。
Mutex(Mutal Exclusion = 互斥锁)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。
#!/usr/bin/ruby require 'thread' count1 =2 = difference = 0 counter = Thread.new do loop do count1 += 1 count2 += 1 end end spy = Thread.new do loop do difference += (count1 - count2).abs end end sleep 1 puts "count1 : #{count1" puts "count2 : #{count2" puts "difference : #{difference}"
以上示例运行输出结果为:
count1 : 9712487 count2 : 12501239 difference : 0
#!/usr/bin/ruby require 'thread' mutex = Mutex.new count1 =2 = difference = 0 counter = Thread.new do loop do mutex.synchronize do count1 += 1 count2 += 1 end end end spy = Thread.new do loop do mutex.synchronize do difference += (count1 - count2).abs end end end sleep 1 mutex.lock puts "count1 : #{count1" puts "count2 : #{count2" puts "difference : #{difference}"
以上示例运行输出结果为:
count1 : 1336406 count2 : 1336406 difference : 0
两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,这种状况,就称为死锁。
例如,一个进程 p1占用了显示器,同时又必须使用打印机,而打印机被进程p2占用,p2又必须使用显示器,这样就形成了死锁。
当我们在使用 Mutex 对象时需要注意线程死锁。
#!/usr/bin/ruby require 'thread' mutex = Mutex.new cv = ConditionVariable.new a = Thread.new { mutex.synchronize { puts "A: Tengo la sección crítica, pero esperaré a cv" cv.wait(mutex) puts "A: ¡Tengo la sección crítica de nuevo! ¡Yo mando!" } } puts "(Más tarde, de vuelta en la granja...)" b = Thread.new { mutex.synchronize { puts "B: Now I am critical, but I am done with cv" cv.signal puts "B: I am still critical, finishing up" } } a.join b.join
The output result of the above example is as follows:
A: I have critical section, but will wait for cv (Later, back at the ranch...) B: Now I am critical, but I am done with cv B: I am still critical, finishing up A: I have critical section again! I rule!
The complete Thread (thread) class methods are as follows:
Número | Descripción del método |
---|---|
1 | Thread.abort_on_exception If its value is true, once a thread terminates due to an exception, the entire interpreter will be interrupted. Its default value is false, which means that in normal circumstances, if a thread occurs an exception and the exception is not detected by Thread#join, etc., the thread will be terminated without warning. |
2 | Thread.abort_on_exception= If set to trueOnce a thread terminates due to an exception, the entire interpreter will be interrupted. Return the new state |
3 | Thread.critical Devolver un valor booleano. |
4 | Thread.critical= When its value is true, thread switching will not be performed. If the current thread is suspended (stop) or interrupted by a signal, its value will automatically become false. |
5 | Thread.current Return the currently running thread (current thread). |
6 | Thread.exit Terminate the execution of the current thread and return the current thread. If the current thread is the only one, it will use exit(0) to terminate its execution. |
7 | Thread.fork { block } Generate a thread like Thread.new. |
8 | Thread.kill( aThread ) Terminate the execution of the thread. |
9 | Thread.list Return an array of active threads that are running or suspended. |
10 | Thread.main Return to the main thread. |
11 | Thread.new( [arg])* ) {| args | block } Crear un hilo y comenzar a ejecutar. Los argumentos se pasarán sin cambios al bloque. Esto permite pasar valores a las variables locales propias del hilo al iniciar el hilo. |
12 | Thread.pass Transfer the running right to other threads. It does not change the state of the running thread, but gives control to other runnable threads (explicit thread scheduling). |
13 | Thread.start( [ args ])* ) {| args | block } Crear un hilo y comenzar a ejecutar. Los argumentos se pasarán sin cambios al bloque. Esto permite pasar valores a las variables locales propias del hilo al iniciar el hilo. |
14 | Thread.stop Suspender el hilo actual hasta que otro hilo despierte el hilo utilizando el método run. |
A continuación se muestra un ejemplo que llama al método de ejemplo de hilo join:
#!/usr/bin/ruby thr = Thread.new do # Ejemplo puts "En el segundo hilo" raise "Lanzar excepción" end thr.join # Ejemplo de llamada al método de ejemplo join
A continuación se muestra una lista de métodos de ejemplo completa:
Número | Descripción del método |
---|---|
1 | thr[ name ] Obtener los datos固有del hilo correspondientes al nombre. name puede ser una cadena o un símbolo. Si no hay datos correspondientes al nombre, devuelve nil. |
2 | thr[ name ]= Establecer el valor de los datos固有del hilo correspondientes al nombre, name puede ser una cadena o un símbolo. Si se establece en nil, se eliminarán los datos correspondientes del hilo. |
3 | thr.abort_on_exception Devolver un valor booleano. |
4 | thr.abort_on_exception= Si su valor es true, el intérprete completo se interrumpirá una vez que algún hilo termine debido a una excepción. |
5 | thr.alive? Si el hilo está "vivo", devuelve true. |
6 | thr.exit Terminar la ejecución del hilo. Devolver self. |
7 | thr.join Suspender el hilo actual hasta que el hilo self termine su ejecución. Si self termina debido a una excepción, se generará la misma excepción en el hilo actual. |
8 | thr.key? Si los datos固有del hilo correspondientes al nombre ya han sido definidos, se devuelve true |
9 | thr.kill Similares a Thread.exit 。 |
10 | thr.priority Devolver la prioridad del hilo. El valor predeterminado de la prioridad es 0. Cuanto mayor sea este valor, mayor será la prioridad. |
11 | thr.priority= Establecer la prioridad del hilo. También se puede establecer en un número negativo. |
12 | thr.raise( anException ) Llamar forzadamente una excepción en este hilo. |
13 | thr.run Reiniciar el hilo suspendido (stop). A diferencia de wakeup, realiza la transición de hilo inmediatamente. Si se utiliza este método en un proceso muerto, se generará una excepción ThreadError. |
14 | thr.safe_level Retorna el nivel de seguridad de self. El nivel de safe_level de la línea actual es el mismo que $SAFE. |
15 | thr.status Usa las cadenas "run", "sleep" o "aborting" para representar el estado del hilo activo. Si un hilo termina normalmente, devuelve false. Si termina debido a una excepción, devuelve nil. |
16 | thr.stop? Retorna true si el hilo está en estado de finalización (dead) o suspendido (stop). |
17 | thr.value Esperar hasta que el hilo self termine de ejecutar (equivalente a join) y regresar el valor del bloque del hilo. Si se produce una excepción durante la ejecución del hilo, se volverá a lanzar la excepción. |
18 | thr.wakeup Cambiar el estado del hilo suspendido (stop) a estado ejecutable (run). Si se llama a este método en un hilo muerto, se generará una excepción ThreadError. |