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.start和Thread.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参考,请查阅手册。