Ruby multithreading


Setiap program yang dijalankan pada sistem adalah satu proses. Setiap proses mengandungi satu atau lebih utas.

Benang ialah proses kawalan berjujukan tunggal dalam program Menjalankan berbilang benang pada masa yang sama untuk menyelesaikan tugasan yang berbeza dalam satu program dipanggil berbilang benang.

Dalam Ruby, kita boleh mencipta berbilang benang melalui kelas Benang Ruby adalah ringan dan boleh melaksanakan kod selari dengan cara yang cekap.


Buat utas Ruby

Untuk memulakan utas baharu, cuma panggil Thread.new:

# 线程 #1 代码部分
Thread.new {
  # 线程 #2 执行代码
}
# 线程 #1 执行代码

Instance

Contoh berikut Menunjukkan cara untuk gunakan multi-threading dalam program 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}"
t1=Thread.new{func1()}
t2=Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"


Hasil pelaksanaan kod di atas ialah:

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

Kitaran hayat thread

1 , Thread boleh dibuat menggunakan Thread.new, dan thread juga boleh dibuat menggunakan tiga kaedah Thread.start atau Thread.fork dengan sintaks yang sama.

2. Tidak perlu memulakan utas selepas menciptanya, utas akan dilaksanakan secara automatik.

3. Kelas Thread mentakrifkan beberapa kaedah untuk mengawal utas. Benang melaksanakan blok kod dalam Thread.new.

4. Pernyataan terakhir dalam blok kod utas ialah nilai utas, yang boleh dipanggil melalui kaedah utas Jika pelaksanaan utas selesai, nilai utas akan dikembalikan, jika tidak, nilainya tidak akan dikembalikan sehingga pelaksanaan benang selesai.

5. Kaedah Thread.current mengembalikan objek yang mewakili urutan semasa. Kaedah benang.utama mengembalikan benang utama.

6. Jalankan utas melalui kaedah Thread.Join Kaedah ini akan menangguhkan utas utama sehingga utas semasa menyelesaikan pelaksanaan.


Status utas

Benang mempunyai 5 keadaan:

线程状态返回值
可执行run
睡眠Sleeping
退出aborting
正常终止false
发生异常终止nil

Benang dan pengecualian

Apabila pengecualian berlaku dalam utas dan tidak ditangkap oleh penyelamatan, utas biasanya akan ditamatkan tanpa amaran. Walau bagaimanapun, jika utas lain telah menunggu untuk utas ini kerana Thread#join, utas menunggu juga akan dilemparkan pengecualian yang sama.

begin
  t = Thread.new do
    Thread.pass    # 主线程确实在等join
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

Gunakan 3 kaedah berikut untuk membenarkan penterjemah mengganggu apabila urutan ditamatkan kerana pengecualian.

  • Nyatakan pilihan -d apabila memulakan skrip dan jalankannya dalam mod nyahpepijat.

  • Tetapkan bendera dengan Thread.abort_on_exception.

  • Gunakan Thread#abort_on_exception untuk menetapkan bendera bagi urutan yang ditentukan.

Apabila salah satu daripada 3 kaedah di atas digunakan, keseluruhan jurubahasa akan terganggu.

t = Thread.new { ... }
t.abort_on_exception = true

Kawalan penyegerakan benang

Dalam Ruby, tiga cara untuk mencapai penyegerakan disediakan, iaitu:

1. Penyegerakan benang melalui kelas Mutex

2. Kelas Queue yang menyelia pemindahan data merealisasikan penyegerakan benang

3 Gunakan ConditionVariable untuk merealisasikan kawalan penyegerakan

Penyegerakan thread direalisasikan melalui kelas Mutex

Ia adalah. direalisasikan melalui kawalan penyegerakan Benang kelas Mutex, jika pembolehubah program diperlukan dalam berbilang utas pada masa yang sama, pembolehubah ini boleh dikunci sebahagiannya menggunakan kunci. Kodnya adalah seperti berikut:

#!/usr/bin/ruby

require "thread"
puts "Synchronize Thread"

@num=200
@mutex=Mutex.new

def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "you have successfully bought #{num} tickets"
          else
               puts "sorry,no enough tickets"
          end
     @mutex.unlock
end

ticket1=Thread.new 10 do
     10.times do |value|
     ticketNum=15
     buyTicket(ticketNum)
     sleep 0.01
     end
end

ticket2=Thread.new 10 do
     10.times do |value|
     ticketNum=20
     buyTicket(ticketNum)
     sleep 0.01
     end
end

sleep 1
ticket1.join
ticket2.join


Hasil pelaksanaan kod di atas ialah:

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

Selain menggunakan kunci untuk mengunci pembolehubah, anda juga boleh gunakan try_lock untuk mengunci pembolehubah, dan anda juga boleh menggunakan Mutex menyegerakkan akses kepada pembolehubah.

Kelas Baris Gilir yang menyelia pemindahan data melaksanakan penyegerakan benang

Kelas Baris Gilir mewakili baris gilir yang menyokong urutan dan boleh mengakses penghujung baris gilir secara serentak. Urutan yang berbeza boleh menggunakan kelas pasangan yang sama, tetapi anda tidak perlu risau sama ada data dalam baris gilir ini boleh disegerakkan Selain itu, menggunakan kelas SizedQueue boleh mengehadkan panjang baris gilir

The SizedQueue. kelas boleh membantu kami membangunkan aplikasi penyegerakan benang. Program ini tidak perlu risau tentang penyegerakan benang selagi ia menyertai baris gilir ini.

Masalah pengeluar-pengguna klasik:

#!/usr/bin/ruby

require "thread"
puts "SizedQuee Test"

queue = Queue.new

producer = Thread.new do
     10.times do |i|
          sleep rand(i) # 让线程睡眠一段时间
          queue << i
          puts "#{i} produced"
     end
end

consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     end
end

consumer.join


Output program:

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

Pembolehubah benang

Benang boleh mempunyai pembolehubah peribadinya sendiri Pembolehubah peribadi utas ditulis pada utas apabila utas dibuat. Boleh digunakan dalam skop utas, tetapi tidak boleh dikongsi di luar utas.

Tetapi kadangkala, apakah yang perlu saya lakukan jika pembolehubah setempat sesuatu utas perlu diakses oleh utas lain atau utas utama? Ruby menyediakan cara untuk mencipta pembolehubah benang mengikut nama, sama seperti merawat benang sebagai jadual cincang. Tulis data melalui []= dan baca data melalui []. Mari kita lihat kod berikut:

#!/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}"

Hasil keluaran kod di atas ialah:

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

Benang utama menunggu sub-benang menyelesaikan pelaksanaan, dan kemudian mengeluarkan setiap nilai secara berasingan. .


Keutamaan utas

Keutamaan utas ialah faktor utama yang mempengaruhi penjadualan utas. Faktor lain termasuk tempoh masa pelaksanaan yang diduduki oleh CPU, penjadualan kumpulan benang, dsb.

Anda boleh menggunakan kaedah Thread.priority untuk mendapatkan keutamaan thread dan kaedah Thread.priority= untuk melaraskan keutamaan thread.

Keutamaan utas ialah 0 secara lalai. Mereka yang mempunyai keutamaan yang lebih tinggi melaksanakan lebih cepat.

A Thread boleh mengakses semua data dalam skopnya sendiri, tetapi apakah yang perlu anda lakukan jika anda perlu mengakses data daripada thread lain dalam thread? Kelas Thread menyediakan kaedah untuk data thread untuk mengakses satu sama lain Anda hanya boleh menggunakan thread sebagai jadual Hash Anda boleh menggunakan []= untuk menulis data dan [] untuk membaca data dalam mana-mana thread.

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"]}" }

Seperti yang anda lihat, dengan menganggap urutan sebagai jadual Hash dan menggunakan kaedah [] dan []=, kami menyedari perkongsian data antara urutan.


Pengecualian bersama benang

Mutex (Mutal Exclusion = mutex lock) ialah sejenis kunci yang digunakan dalam pengaturcaraan berbilang benang untuk menghalang dua utas daripada mengakses sumber awam yang sama (seperti pembolehubah global) pada masa yang sama ) mekanisme untuk membaca dan menulis.

Instance tanpa Mutax

#!/usr/bin/ruby
require 'thread'

count1 = count2 = 0
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}"

Hasil keluaran dari contoh di atas ialah:

count1 :  9712487
count2 :  12501239
difference : 0

Instance menggunakan Mutax

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new

count1 = count2 = 0
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}"

Hasil output dari contoh di atas ialah :

count1 :  1336406
count2 :  1336406
difference : 0

Kebuntuan

Dua atau lebih unit pengkomputeran, kedua-dua pihak menunggu yang lain untuk berhenti berjalan untuk mendapatkan sumber sistem, tetapi apabila tidak ada pihak yang keluar awal, keadaan ini adalah. dipanggil kebuntuan.

Sebagai contoh, proses p1 menduduki monitor dan mesti menggunakan pencetak pada masa yang sama, dan pencetak diduduki oleh proses p2, dan p2 mesti menggunakan monitor, sekali gus membentuk kebuntuan.

Kita perlu memberi perhatian kepada kebuntuan benang apabila menggunakan objek Mutex.

Instance

#!/usr/bin/ruby
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

Hasil keluaran contoh di atas ialah:

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!

Kaedah kelas thread

Kaedah kelas Thread yang lengkap adalah seperti berikut:

序号方法描述
1Thread.abort_on_exception
若其值为真的话,一旦某线程因异常而终止时,整个解释器就会被中断。它的默认值是假,也就是说,在通常情况下,若某线程发生异常且该异常未被Thread#join等检测到时,该线程会被无警告地终止。
2Thread.abort_on_exception=
如果设置为 true, 一旦某线程因异常而终止时,整个解释器就会被中断。返回新的状态
3Thread.critical
返回布尔值。
4Thread.critical=
当其值为true时,将不会进行线程切换。若当前线程挂起(stop)或有信号(signal)干预时,其值将自动变为false。
5Thread.current
返回当前运行中的线程(当前线程)。
6Thread.exit
终止当前线程的运行。返回当前线程。若当前线程是唯一的一个线程时,将使用exit(0)来终止它的运行。
7Thread.fork { block }
与 Thread.new 一样生成线程。
8Thread.kill( aThread )
终止线程的运行.
9Thread.list
返回处于运行状态或挂起状态的活线程的数组。
10Thread.main
返回主线程。
11Thread.new( [ arg ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
12Thread.pass
将运行权交给其他线程. 它不会改变运行中的线程的状态,而是将控制权交给其他可运行的线程(显式的线程调度)。
13Thread.start( [ args ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
14Thread.stop
将当前线程挂起,直到其他线程使用run方法再次唤醒该线程。

Kaedah instantiation thread

Contoh berikut memanggil kaedah instantiation thread join:

#!/usr/bin/ruby

thr = Thread.new do   # 实例化
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # 调用实例化方法 join

Berikut ialah senarai lengkap kaedah instantiation:

序号方法描述
1thr[ name ]
取出线程内与name相对应的固有数据。 name可以是字符串或符号。 若没有与name相对应的数据时, 返回nil。
2thr[ name ] =
设置线程内name相对应的固有数据的值, name可以是字符串或符号。 若设为nil时, 将删除该线程内对应数据。
3thr.abort_on_exception
返回布尔值。
4thr.abort_on_exception=
若其值为true的话,一旦某线程因异常而终止时,整个解释器就会被中断。
5thr.alive?
若线程是"活"的,就返回true。
6thr.exit
终止线程的运行。返回self。
7thr.join
挂起当前线程,直到self线程终止运行为止. 若self因异常而终止时, 将会当前线程引发同样的异常。
8thr.key?
若与name相对应的线程固有数据已经被定义的话,就返回true
9thr.kill
类似于 Thread.exit
10thr.priority
返回线程的优先度. 优先度的默认值为0. 该值越大则优先度越高.
11thr.priority=
设定线程的优先度. 也可以将其设定为负数.
12thr.raise( anException )
在该线程内强行引发异常.
13thr.run
重新启动被挂起(stop)的线程. 与wakeup不同的是,它将立即进行线程的切换. 若对死进程使用该方法时, 将引发ThreadError异常.
14thr.safe_level
返回self 的安全等级. 当前线程的safe_level与$SAFE相同.
15thr.status
使用字符串"run"、"sleep"或"aborting" 来表示活线程的状态. 若某线程是正常终止的话,就返回false. 若因异常而终止的话,就返回nil。
16thr.stop?
若线程处于终止状态(dead)或被挂起(stop)时,返回true.
17thr.value
一直等到self线程终止运行(等同于join)后,返回该线程的块的返回值. 若在线程的运行过程中发生了异常, 就会再次引发该异常.
18thr.wakeup
把被挂起(stop)的线程的状态改为可执行状态(run), 若对死线程执行该方法时,将会引发ThreadError异常。