Ruby 多线程

  • 多线程

    传统程序只有一个执行线程,构成该程序的语句或指令将顺序执行,直到程序终止。多线程程序具有多个执行线程。在每个线程中,语句是按顺序执行的,但是线程本身可以在例如多核CPU上并行执行。通常在单个CPU计算机上,实际上不是并行执行多个线程,而是通过交错执行线程来模拟并行性。Ruby使使用Thread类编写多线程程序变得容易。Ruby线程是在代码中实现并发的轻量级高效方法。
  • 创建Ruby线程

    
    # Thread #1 is running here
    Thread.new {
       # Thread #2 runs this code
    }
    # Thread #1 runs this code
    
    这是一个示例,显示了如何使用多线程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}"
    t1 = Thread.new{func1()}
    t2 = Thread.new{func2()}
    t1.join
    t2.join
    puts "End at #{Time.now}"
    
    这将产生以下结果-
    
    Started At 2020-08-18 08:42:30 +0800
    func1 at: 2020-08-18 08:42:30 +0800
    func2 at: 2020-08-18 08:42:30 +0800
    func2 at: 2020-08-18 08:42:31 +0800
    func1 at: 2020-08-18 08:42:32 +0800
    func2 at: 2020-08-18 08:42:32 +0800
    func1 at: 2020-08-18 08:42:34 +0800
    End at 2020-08-18 08:42:36 +0800
    
  • 线程生命周期

    使用Thread.new创建一个新线程。您还可以使用同义词Thread.startThread.fork。创建线程后无需启动线程,当CPU资源可用时,它将自动开始运行。Thread类定义了许多在线程运行时查询和操作线程的方法。线程在与Thread.new调用关联的块中运行代码,然后停止运行。该块中最后一个表达式的值就是线程的值,可以通过调用Thread对象的value方法获得。如果线程已运行完毕,则该值立即返回线程的值。否则,value方法将阻塞并且直到线程完成后才返回。类方法Thread.current返回表示当前线程的Thread对象。这允许线程进行自我操作。类方法Thread.main返回代表主线程的Thread对象。这是从Ruby程序启动时开始的执行线程。您可以通过调用特定线程的Thread.join方法来等待该线程完成。调用线程将阻塞,直到给定线程完成。
  • 线程和异常

    如果在主线程中引发异常,并且在任何地方都没有处理,那么Ruby解释器将打印一条消息并退出。在主线程以外的线程中,未处理的异常会导致线程停止运行。如果一个线程t由于未处理异常而退出,另一个线程s调用join(t)或t.value,则在t中发生的异常会在线程s中引发。如果线程。默认条件是abort_on_exception为false,未处理的异常会终止当前线程,其余线程继续运行。如果您希望任何线程中的任何未处理异常导致解释器退出,请设置类方法thread.abort_on_exception为true。
    
    t = Thread.new { ... }
    t.abort_on_exception = true
    
  • 线程变量

    创建线程时,线程通常可以访问范围内的任何变量。线程块局部的变量是线程局部的,并且不共享。Thread类具有特殊的功能,该功能允许按名称创建和访问线程局部变量。您只需将线程对象视为哈希对象即可,使用[]=写入元素,然后使用[]读回它们。在此示例中,每个线程都使用键mycount将变量count的当前值记录在线程局部变量中。
    
    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}"
    
    尝试一下
    主线程等待子线程完成,然后打印出每个子线程捕获的计数值。
  • 线程优先级

    影响线程调度的第一个因素是线程优先级:高优先级线程先于低优先级线程进行调度。更准确地说,只有在没有更高优先级的线程在等待运行时,线程才会获得CPU时间。您可以使用priority=和priority设置和查询Ruby Thread对象的优先级。新创建的线程与创建它的线程具有相同的优先级。主线程从优先级0开始。在开始运行之前,无法设置线程的优先级。但是,线程可以将其优先级作为其采取的第一个操作来提高或降低。
  • 线程互斥

    如果两个线程共享对同一数据的访问,并且至少有一个线程修改了该数据,则必须格外小心,以确保没有线程能够看到处于不一致状态的数据。这称为线程互斥。Mutex是一个类,它实现简单的信号锁以互斥访问某些共享资源。也就是说,在给定的时间只有一个线程可以持有该锁。其他线程可能选择排队等待该锁变为可用,或者可能只是选择立即获得一个指示该锁不可用的错误。通过将对共享数据的所有访问置于互斥锁的控制之下,我们确保了一致性和原子操作。让我们尝试示例,第一个不带mutax,第二个不带mutax-
    没有Mutax的示例
    
    require 'thread'
    
    count1 = count2 = 0
    difference = 0
    
    Thread.new do
       loop do
          count1 += 1
          count2 += 1
       end
    end
    
    Thread.new do
       loop do
          difference += (count1 - count2).abs
       end
    end
    
    sleep 1
    
    puts "count1 :  #{count1}"
    puts "count2 :  #{count2}"
    puts "difference : #{difference}"
    
    尝试一下
    有Mutax的示例同一时间只能有一个线程对变量进行更改
    
    require 'thread'
    mutex = Mutex.new
    
    count1 = count2 = 0
    difference = 0
    Thread.new do
       loop do
          mutex.synchronize do
             count1 += 1
             count2 += 1
          end
       end
    end
    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}"
    
    尝试一下
  • 处理死锁

    当我们开始使用Mutex对象进行线程堵塞时,我们必须小心避免死锁。死锁是所有线程都在等待获取另一个线程拥有的资源时发生的情况。由于所有线程均被阻止,因此它们无法释放所持有的锁。并且由于它们无法释放这些锁,因此其他任何线程都无法获取这些锁。这是条件变量出现的地方。甲条件变量仅仅是一个与资源相关联,并且在特定的保护内使用的信号的互斥。当您需要不可用的资源时,请等待条件变量。该操作将释放相应互斥锁的锁定。当其他一些线程发出信号表明资源可用时,原始线程将退出等待状态,同时重新获得对关键区域的锁定。
    
    require 'thread'
    mutex = Mutex.new
    
    cv = ConditionVariable.new
    a = Thread.new {
       mutex.synchronize {
          puts "A: I have critical section, but will wait for cv"
          cv.wait(mutex)
          puts "A: I have critical section again! I rule!"
       }
    }
    
    puts "(Later, back at the ranch...)"
    
    b = Thread.new {
       mutex.synchronize {
          puts "B: Now I am critical, but am done with cv"
          cv.signal
          puts "B: I am still critical, finishing up"
       }
    }
    a.join
    b.join
    
    尝试一下
  • 线程状态

    下表列出了与五个可能状态相对应的五个可能返回值。该状态方法返回线程的状态。
    状态 返回值
    可运行 run
    睡眠 Sleeping
    堕胎 aborting
    正常终止 false
    异常终止 nil
    提示:更多的的Thread参考,请查阅手册