Ruby - Đa luồng
Các chương trình truyền thống có một luồng thực thi duy nhất, các câu lệnh hoặc lệnh bao gồm chương trình được thực thi tuần tự cho đến khi chương trình kết thúc.
Một chương trình đa luồng có nhiều hơn một luồng thực thi. Trong mỗi luồng, các câu lệnh được thực thi tuần tự, nhưng bản thân các luồng có thể được thực thi song song trên một CPU đa lõi chẳng hạn. Thường thì trên một máy CPU, nhiều luồng không thực sự được thực thi song song, mà sự song song được mô phỏng bằng cách xen kẽ việc thực thi của các luồng.
Ruby giúp dễ dàng viết các chương trình đa luồng với lớp Thread . Các chuỗi Ruby là một cách nhẹ và hiệu quả để đạt được tính đồng thời trong mã của bạn.
Tạo chuỗi Ruby
Để bắt đầu một chuỗi mới, chỉ cần liên kết một khối với một lệnh gọi tới Thread.new . Một luồng mới sẽ được tạo để thực thi mã trong khối và luồng gốc sẽ trở lại từ Thread.new ngay lập tức và tiếp tục thực thi với câu lệnh tiếp theo -
# Thread #1 is running here
Thread.new {
# Thread #2 runs this code
}
# Thread #1 runs this code
Thí dụ
Đây là một ví dụ cho thấy chúng ta có thể sử dụng chương trình Ruby đa luồng như thế nào.
#!/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}"
Điều này sẽ tạo ra kết quả sau:
Started At Wed May 14 08:21:54 -0700 2008
func1 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:55 -0700 2008
func1 at: Wed May 14 08:21:56 -0700 2008
func2 at: Wed May 14 08:21:56 -0700 2008
func1 at: Wed May 14 08:21:58 -0700 2008
End at Wed May 14 08:22:00 -0700 2008
Vòng đời của chuỗi
Một chủ đề mới được tạo với Thread.new . Bạn cũng có thể sử dụng các từ đồng nghĩa Thread.start và Thread.fork .
Không cần bắt đầu một luồng sau khi tạo nó, nó bắt đầu chạy tự động khi tài nguyên CPU có sẵn.
Lớp Thread định nghĩa một số phương thức để truy vấn và thao tác với luồng khi nó đang chạy. Một luồng chạy mã trong khối được liên kết với lệnh gọi tới Thread.new và sau đó nó ngừng chạy.
Giá trị của biểu thức cuối cùng trong khối đó là giá trị của luồng và có thể nhận được bằng cách gọi phương thức giá trị của đối tượng Luồng . Nếu luồng đã hoàn thành, thì giá trị trả về giá trị của luồng ngay lập tức. Nếu không, phương thức giá trị sẽ chặn và không trả về cho đến khi luồng hoàn thành.
Phương thức lớp Thread.current trả về đối tượng Thread đại diện cho luồng hiện tại. Điều này cho phép các chủ đề tự thao tác. Phương thức lớp Thread.main trả về đối tượng Thread đại diện cho luồng chính. Đây là luồng thực thi ban đầu bắt đầu khi chương trình Ruby được khởi động.
Bạn có thể đợi một luồng cụ thể kết thúc bằng cách gọi phương thức Thread.join của luồng đó . Luồng đang gọi sẽ chặn cho đến khi kết thúc luồng đã cho.
Chủ đề và ngoại lệ
Nếu một ngoại lệ được đưa ra trong chuỗi chính và không được xử lý ở bất kỳ đâu, trình thông dịch Ruby sẽ in một thông báo và thoát. Trong các luồng, ngoài luồng chính, các ngoại lệ không được xử lý khiến luồng ngừng chạy.
Nếu một chủ đề t thoát vì một ngoại lệ chưa được xử lý và một chuỗi khác sgọi t.join hoặc t.value, sau đó là ngoại lệ xảy ra trongt được nâng lên trong chủ đề s.
Nếu Thread.abort_on_exception là false , điều kiện mặc định, một ngoại lệ không được xử lý chỉ đơn giản là giết luồng hiện tại và tất cả phần còn lại tiếp tục chạy.
Nếu bạn muốn bất kỳ ngoại lệ chưa được xử lý nào trong bất kỳ luồng nào khiến trình thông dịch thoát ra, hãy đặt phương thức lớp Thread.abort_on_exception thành true .
t = Thread.new { ... }
t.abort_on_exception = true
Biến chuỗi
Một luồng thường có thể truy cập bất kỳ biến nào trong phạm vi khi luồng được tạo. Các biến cục bộ đối với khối của một chuỗi là cục bộ của chuỗi và không được chia sẻ.
Lớp luồng có một cơ sở đặc biệt cho phép các biến cục bộ của luồng được tạo và truy cập bằng tên. Bạn chỉ cần coi đối tượng luồng như thể nó là một Hash, ghi vào các phần tử bằng cách sử dụng [] = và đọc lại chúng bằng cách sử dụng [].
Trong ví dụ này, mỗi luồng ghi lại giá trị hiện tại của số lượng biến trong một biến threadlocal với mycount chính .
#!/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}"
Điều này tạo ra kết quả sau:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
Luồng chính đợi các luồng phụ kết thúc và sau đó in ra giá trị của số lượng được bắt bởi mỗi luồng .
Ưu tiên chuỗi
Yếu tố đầu tiên ảnh hưởng đến việc lập lịch luồng là mức độ ưu tiên của luồng: các luồng có mức độ ưu tiên cao được lập lịch trước các luồng có mức độ ưu tiên thấp. Chính xác hơn, một luồng sẽ chỉ nhận được thời gian CPU nếu không có luồng có mức độ ưu tiên cao hơn đang chờ chạy.
Bạn có thể đặt và truy vấn mức độ ưu tiên của một đối tượng Ruby Thread với ưu tiên = và ưu tiên . Một luồng mới được tạo bắt đầu ở cùng mức ưu tiên với luồng đã tạo nó. Luồng chính bắt đầu ở mức ưu tiên 0.
Không có cách nào để đặt mức độ ưu tiên của một luồng trước khi nó bắt đầu chạy. Tuy nhiên, một luồng có thể tăng hoặc giảm mức độ ưu tiên của chính nó như là hành động đầu tiên mà nó thực hiện.
Loại trừ chủ đề
Nếu hai luồng chia sẻ quyền truy cập vào cùng một dữ liệu và ít nhất một trong các luồng sửa đổi dữ liệu đó, bạn phải đặc biệt lưu ý để đảm bảo rằng không có luồng nào có thể xem dữ liệu ở trạng thái không nhất quán. Đây được gọi là loại trừ luồng .
Mutexlà một lớp thực hiện một khóa semaphore đơn giản để truy cập loại trừ lẫn nhau vào một số tài nguyên được chia sẻ. Nghĩa là, chỉ một luồng có thể giữ khóa tại một thời điểm nhất định. Các chủ đề khác có thể chọn xếp hàng chờ khóa khả dụng hoặc có thể đơn giản chọn nhận lỗi ngay lập tức cho biết khóa không khả dụng.
Bằng cách đặt tất cả quyền truy cập vào dữ liệu được chia sẻ dưới sự kiểm soát của mutex , chúng tôi đảm bảo tính nhất quán và hoạt động nguyên tử. Hãy thử lấy các ví dụ, ví dụ đầu tiên không có mutax và ví dụ thứ hai có mutax -
Ví dụ không có 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}"
Điều này sẽ tạo ra kết quả sau:
count1 : 1583766
count2 : 1583766
difference : 0
#!/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}"
Điều này sẽ tạo ra kết quả sau:
count1 : 696591
count2 : 696591
difference : 0
Xử lý bế tắc
Khi bắt đầu sử dụng các đối tượng Mutex để loại trừ luồng, chúng ta phải cẩn thận để tránh bế tắc . Bế tắc là điều kiện xảy ra khi tất cả các luồng đang chờ lấy một tài nguyên do một luồng khác nắm giữ. Bởi vì tất cả các luồng bị chặn, họ không thể giải phóng ổ khóa mà họ giữ. Và bởi vì chúng không thể giải phóng các ổ khóa, không một luồng nào khác có thể có được những ổ khóa đó.
Đây là lúc các biến điều kiện đi vào hình ảnh. Một biến điều kiện chỉ đơn giản là một semaphore được liên kết với một tài nguyên và được sử dụng trong phạm vi bảo vệ của một đặc biệt mutex . Khi bạn cần một tài nguyên không có sẵn, bạn đợi một biến điều kiện. Hành động đó sẽ giải phóng khóa trên mutex tương ứng . Khi một số luồng khác báo hiệu rằng tài nguyên có sẵn, luồng ban đầu sẽ không chờ đợi và đồng thời lấy lại khóa trên vùng quan trọng.
Thí dụ
#!/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
Điều này sẽ tạo ra kết quả sau:
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!
Tiểu bang
Có năm giá trị trả về có thể có tương ứng với năm trạng thái có thể được hiển thị trong bảng sau. Các tình trạng phương thức trả về trạng thái của chủ đề.
Trạng thái chuỗi | Giá trị trả lại |
---|---|
Runnable | chạy |
Đang ngủ | Đang ngủ |
Hủy bỏ | phá thai |
Kết thúc bình thường | sai |
Đã chấm dứt với ngoại lệ | không |
Phương thức lớp luồng
Các phương thức sau được cung cấp bởi lớp Thread và chúng có thể áp dụng cho tất cả các luồng có sẵn trong chương trình. Các phương thức này sẽ được gọi bằng cách sử dụng tên lớp Thread như sau:
Thread.abort_on_exception = true
Sr.No. | Phương pháp & Mô tả |
---|---|
1 | Thread.abort_on_exception Trả về trạng thái hủy bỏ toàn cục với điều kiện ngoại lệ . Giá trị mặc định là sai . Khi được đặt thành true , sẽ khiến tất cả các luồng bị hủy bỏ (quá trình sẽ thoát (0)) nếu một ngoại lệ được đưa ra trong bất kỳ luồng nào |
2 | Thread.abort_on_exception= Khi được đặt thành true , tất cả các luồng sẽ hủy bỏ nếu một ngoại lệ được đưa ra. Trả về trạng thái mới. |
3 | Thread.critical Trả về trạng thái của điều kiện quan trọng của chuỗi chung . |
4 | Thread.critical= Đặt trạng thái của điều kiện quan trọng của chuỗi chung và trả về nó. Khi được đặt thành true , sẽ cấm lập lịch cho bất kỳ luồng nào hiện có. Không chặn các chủ đề mới được tạo và chạy. Các hoạt động nhất định của chuỗi (chẳng hạn như dừng hoặc hủy một chuỗi, ngủ trong chuỗi hiện tại và đưa ra một ngoại lệ) có thể khiến một chuỗi được lên lịch ngay cả khi ở trong phần quan trọng. |
5 | Thread.current Trả về luồng hiện đang thực thi. |
6 | Thread.exit Chấm dứt luồng hiện đang chạy và lên lịch chạy luồng khác. Nếu luồng này đã được đánh dấu là bị hủy, thoát trả về Chủ đề. Nếu đây là luồng chính hoặc luồng cuối cùng, hãy thoát khỏi quy trình. |
7 | Thread.fork { block } Từ đồng nghĩa với Thread.new. |
số 8 | Thread.kill( aThread ) Làm cho một Chủ đề đã cho để thoát |
9 | Thread.list Returns một loạt các chủ đề đối tượng cho tất cả các chủ đề đó là một trong hai Runnable hoặc dừng lại. Chủ đề. |
10 | Thread.main Trả về luồng chính cho quy trình. |
11 | Thread.new( [ arg ]* ) {| args | block } Tạo một chuỗi mới để thực thi các hướng dẫn được đưa ra trong khối và bắt đầu chạy nó. Bất kỳ đối số nào được chuyển đến Thread.new đều được chuyển vào khối. |
12 | Thread.pass Gọi bộ lập lịch luồng chuyển việc thực thi cho một luồng khác. |
13 | Thread.start( [ args ]* ) {| args | block } Về cơ bản giống với Thread.new . Tuy nhiên, nếu Thread của lớp là lớp con, thì việc gọi start trong lớp con đó sẽ không gọi phương thức khởi tạo của lớp con . |
14 | Thread.stop Dừng thực thi luồng hiện tại, đưa nó vào trạng thái ngủ và lên lịch thực thi một luồng khác. Đặt lại điều kiện quan trọng thành false. |
Phương thức phiên bản chủ đề
Các phương pháp này có thể áp dụng cho một phiên bản của một luồng. Các phương thức này sẽ được gọi bằng cách sử dụng một thể hiện của một Luồng như sau:
#!/usr/bin/ruby
thr = Thread.new do # Calling a class method new
puts "In second thread"
raise "Raise exception"
end
thr.join # Calling an instance method join
Sr.No. | Phương pháp & Mô tả |
---|---|
1 | thr[ aSymbol ] Tham chiếu thuộc tính - Trả về giá trị của biến luồng cục bộ, sử dụng ký hiệu hoặc tên aSymbol . Nếu biến được chỉ định không tồn tại, trả về nil . |
2 | thr[ aSymbol ] = Gán thuộc tính - Đặt hoặc tạo giá trị của biến cục bộ luồng, sử dụng ký hiệu hoặc chuỗi. |
3 | thr.abort_on_exception Trả về trạng thái hủy bỏ với điều kiện ngoại lệ cho thr . Giá trị mặc định là sai . |
4 | thr.abort_on_exception= Khi được đặt thành true , tất cả các luồng (bao gồm cả chương trình chính) sẽ hủy bỏ nếu một ngoại lệ được đưa ra trong thr . Quá trình sẽ thoát ra một cách hiệu quả (0) . |
5 | thr.alive? Trả về true nếu thr đang chạy hoặc đang ngủ. |
6 | thr.exit Kết thúc thr và lên lịch chạy một luồng khác. Nếu luồng này đã được đánh dấu là bị hủy, thoát trả về Chủ đề . Nếu đây là luồng chính hoặc luồng cuối cùng, sẽ thoát khỏi quy trình. |
7 | thr.join Luồng gọi sẽ tạm ngừng thực thi và chạy thr . Không trở lại cho đến khi thr thoát. Bất kỳ luồng nào không được tham gia sẽ bị giết khi chương trình chính thoát. |
số 8 | thr.key? Trả về true nếu chuỗi (hoặc ký hiệu) đã cho tồn tại dưới dạng biến cục bộ luồng. |
9 | thr.kill Từ đồng nghĩa với Thread.exit . |
10 | thr.priority Trả về mức độ ưu tiên của thr . Mặc định là 0; luồng ưu tiên cao hơn sẽ chạy trước luồng ưu tiên thấp hơn. |
11 | thr.priority= Đặt mức độ ưu tiên của thr thành Số nguyên. Các luồng có mức ưu tiên cao hơn sẽ chạy trước các luồng có mức ưu tiên thấp hơn. |
12 | thr.raise( anException ) Tăng một ngoại lệ từ thr . Người gọi không phải là Thr . |
13 | thr.run Tỉnh dậy Thr , làm cho nó đủ điều kiện để lập lịch trình. Nếu không nằm trong phần quan trọng, thì hãy gọi bộ lập lịch. |
14 | thr.safe_level Trả về mức an toàn có hiệu lực cho thr . |
15 | thr.status Trả về trạng thái của thr : sleep nếu thr đang ngủ hoặc đang chờ vào I / O, chạy nếu thr đang thực thi, false nếu thr kết thúc bình thường và nil nếu thr kết thúc bằng ngoại lệ. |
16 | thr.stop? Trả về true nếu thr đã chết hoặc đang ngủ. |
17 | thr.value Đợi thr hoàn thành qua Thread.join và trả về giá trị của nó. |
18 | thr.wakeup Marks Thr như đủ điều kiện để lập lịch trình, nó vẫn có thể vẫn bị chặn trên I / O, tuy nhiên. |