Ruby 开发(八) - 线程

292 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

一、线程的创建

线程就是能在一个程序中处理若干控制流的功能,Ruby 中线程是用户级线程并且依赖操作系统,线程是进程中的一个实体,是被系统独立调度和分发的基本单位,线程可与同属一个进程的其他线程共享进程的全部资源,但是线程并不用于系统资源。

线程可以将占据长时间的任务放到后台处理,可以加快程序的运行速度,例如在一些等待的任务上实现用户输入、文件读写以及网络收发数据等,线程也可以释放一些珍贵的资源如内存等。

Ruby 中创建线程只需要实例化 Thread 类即可,同时为线程添加一个作为线程体的代码块。

thread = Thread.new do
  # 线程块代码
end

多个线程可以并发执行

i = 1
t1 = Thread.new 6 do |val|
  while i < val
    puts i
    i = i + 1
  end
end

t2 = Thread.new do
  5.times do |a|
    puts "第 #{a + 1} 次输出"
  end
end

t2.join
t1.join

执行上述代码,输出结果如下:

1
2
3
4
5
第 1 次输出
第 2 次输出
第 3 次输出
第 4 次输出
第 5 次输出

多个线程是交替执行的,而不是按照顺序执行的。

除了 Thead.new 可以创建线程外 Thread 的 start 方法和 fork 方法也可以创建线程

thread = Thread.start "hello" do |value|
  puts "使用 start 方法创建的线程 + #{value}"
end

thread1 = Thread.fork "world" do |value|
  puts "使用 fork 方法中创建的线程 + #{value}"
end

thread.join
thread1.join

执行上述代码,输出结果如下:

使用 start 方法创建的线程 + hello
使用 fork 方法中创建的线程 + world

返回并挂起当前线程

Thread 的 current 方法可以返回当前线程

mainThread = Thread.current
puts "主线程是:" + mainThread.to_s

执行上述代码,输出结果如下:

主线程是:#<Thread:0x00007fd6c205fa50 run>

join 方法可以挂起当前线程,然后执行指定的线程

i = 1
threadJoin = Thread.new 10 do |value|
  while i < value
    puts "#{i}"
    i = i + 1
  end
end

threadJoin.join
threadJoin2 = Thread.new do
  10.times do |a|
    puts "第 #{a} 次输出"
  end
end

threadJoin2.join

执行上述代码,输出结果如下:

1
2
3
4
5
6
7
8
9
第 0 次输出
第 1 次输出
第 2 次输出
第 3 次输出
第 4 次输出
第 5 次输出
第 6 次输出
第 7 次输出
第 8 次输出
第 9 次输出

上述两个线程的执行顺序是按照从上而下执行的,而不是交替执行的,说明使用了 join 方法,当前线程会被挂起,会执行执行的线程,除了 join 方法可以挂起线程,value 方法也可以挂起线程,value 方法可以获取下城的值

显示以及停止线程

使用 pass 方法可以暂停当前线程并且执行其他线程,sleep 方法可以让线程进入休眠状态,wakeup 方法可以唤醒线程。exit 和 kill 均可以停止当前线程

puts "开始时间:" + Time.new.to_s
sleep 2

puts "暂停 2 秒后,当前时间为:" + Time.new.to_s

执行上述代码,输出结果如下:

开始时间:2022-06-17 21:08:34 +0800
暂停 2 秒后,当前时间为:2022-06-17 21:08:36 +0800

二、线程的状态

线程的状态可以通过 status 方法获取,线程的状态有以下几种:

线程状态状态说明
sleep线程处于休眠状态或者处于等待 IO
run线程处于运行状态
aborting线程被取消
false线程正常终止
nil线程被非正常状态终止
threadPass = Thread.start do
  puts "正在运行"
end

t = threadPass.status
puts "当前状态是 #{t}"

执行上述代码,输出结果如下:

当前状态是 run

Thread 类中海油几个类方法,如 main 方法指向进程的主线程。

t1 = Thread.new do
  sleep 2
end

t2 = Thread.new do
  if Thread.current == Thread.main
    puts "当前线程是主线程"
  end
end

num = Thread.list.size
puts "当前线程数量是:#{num}"

if Thread.list.include?(Thread.main)
  puts "现在运行的是主线程"
end

执行上述代码,输出结果如下:

当前线程数量是:3
现在运行的是主线程

Thread 的 list 方法返回的是一个数组,用于存储线程。

同步线程

同步线程,使一组并发进程直接制约而互相发送消息从而进行互相合作、互相等待,使得各个进程按照一定的速度执行你的过程成为进程间的童虎,由于线程共享内存空间,因此使用普通变量就可以进行数据间的交换工作,但是为了使操作的时机得当,需要对线程进行同步。

Ruby 中提供了三种实现线程同步的方法,只负责同步的 Mutex 类中的方法,监管数据交接的 Queue 类中的方法,使用 Condition Variable 类实现同步。

Mutext 类

@num = 200
@mutex = Mutex.new

def ticketNum(num)
  @mutex.lock
  Thread.pass
  if(@num > num)
    @num -= num
    puts "成功购买#{num}张票"
  else
    puts "对不起,购买的#{num}张火车票已经失败"
  end
  @mutex.unlock
end

ticketSuccess = Thread.new(199) {|num| ticketNum(num)}
ticketSuccess1 = Thread.new(2) {|num| ticketNum(num)}
ticketSuccess.join
ticketSuccess1.join

执行上述代码,输出结果如下:

成功购买199张票
对不起,购买的2张火车票已经失败

在上述代码中,通过 Mutex.new 创建了一个 Mutex 对象,然后使用 Mutex 的 lock 方法锁定当前进程,然后再使用 unlock 方法进行解除锁定。如果程序中有多个线程需要同时访问一个程序变脸个,可以将这变量部分使用 lock 方法锁定,才能保证不会发生错误。

Mutex 中除了使用 lock 可以将资源锁定之外,还可以使用 try_lock 方法将资源锁定,对于 lock 来说,使用 try_lock 时,如果另一个资源已经被锁定,他会返回 false。

mutex = Mutex.new
t = Thread.new do
  mutex.lock
  sleep 30
end

t1 = Thread.new do
  if mutex.try_lock
    puts "其他线程没有被占用,可以使用"
  else
    puts "正在使用,无法锁定"
  end
end

还可以使用 synchronize 来锁定线程

@num = 200
@mutex = Mutex.new

def ticketNum(num)
  @mutex.synchronize do
    Thread.pass
    if(@num > num)
      @num -= num
      puts "成功购买#{num}张票"
    else
      puts "对不起,购买的#{num}张火车票已经失败"
    end
  end
end

ticketSuccess = Thread.new(199) {|num| ticketNum(num)}
ticketSuccess1 = Thread.new(2) {|num| ticketNum(num)}
ticketSuccess.join
ticketSuccess1.join

执行上述代码,输出结果如下:

成功购买199张票
对不起,购买的2张火车票已经失败

Queue 类表示一个支持线程的队列,能够对同步队列末尾进行访问,也就是说,不同的线程可以使用同一个队列,但不用单线是否能够同步,使用SizedQueue 类能够限制队列的长度。

require 'thread'

queueR = SizedQueue.new(25)
maxSize = queueR.max

puts "队列中的最大的长度是:#{maxSize}"

queueR.max = 78
maxSize = queueR.max

puts "修改后队列中的最大的长度是:#{maxSize}"

执行上述代码,输出结果如下:

队列中的最大的长度是:25
修改后队列中的最大的长度是:78

除上述之外,还可以使用 ConditionVariable 类实现线程同步。