Luồng và môi trường không đồng bộ ban đầu hơi phức tạp. Nếu không có một mô hình tinh thần tốt để tổ chức tương tác, bạn sẽ dễ gặp rắc rối và kết quả không như mong đợi. Trên hết, việc kiểm tra mã không đồng bộ có thể khó khăn nếu không có các công cụ hoặc mẫu kiểm tra phù hợp.
Suy nghĩ về các luồng với tư cách là con người và các đối tượng được chia sẻ như là "những thứ" có thể được sở hữu giúp tổ chức hoạt động của một hệ thống đa luồng. Trong tập này, chúng ta sẽ đi qua một ví dụ để tìm hiểu tất cả về cách kiểm tra mã Ruby không đồng bộ.
Nếu bạn đang sử dụng Rails hoặc Rack hoặc thực sự là bất kỳ ứng dụng nào làm giao diện người dùng của trình duyệt web thì bạn đang ở trong một môi trường không đồng bộ. Giá đỡ #call
luôn được gọi là không đồng bộ. Vì vậy, cho dù bạn có biết hay không, thì vẫn có khả năng là bạn đang sử dụng các thành phần đa luồng.
Kiểm tra:Kích hoạt, thu thập và kiểm tra
Việc kiểm tra một API gọi lại không đồng bộ có thể được thực hiện đồng bộ bằng cách làm theo một mẫu gồm ba bước; kích hoạt , sưu tầm và kiểm tra . Hãy nghĩ xem mỗi chuỗi là một cá thể riêng biệt và các đối tượng cũng vậy, những thứ có thể thuộc sở hữu của một cá nhân tại một thời điểm.
Chúng tôi sẽ sử dụng ví dụ về Người dơi và 7 bộ quần áo khác nhau của anh ấy. Vì đó là một ví dụ thực tế và chúng ta có thể hiểu tầm quan trọng của việc biết liệu tất cả các bộ quần áo mang theo Alfred có được giặt khi bạn sắp cạn và cứu thành phố hay không.
Ví dụ:Ngày giặt là tại bãi rác
Ví dụ là Alfred đang giặt những bộ quần áo của Người Dơi. SuitWashScheduler là một bộ lập lịch gọi một lệnh gọi lại cho mỗi sự kiện giặt. Bộ lập lịch biểu thực hiện bảy cuộc gọi lại trong khoảng thời gian một giây bắt đầu từ một giây sau khi bắt đầu. Trình kích hoạt là việc tạo ra SuitWashScheduler
.
class SuitWashScheduler
def initialize(cnt)
Thread.new {
cnt.times {
sleep(1.0)
yield
}
}
end
end
Thu thập
Việc thu thập kết quả phải an toàn để tránh các điều kiện của cuộc đua. Bất kỳ đối tượng nào được chia sẻ trên nhiều luồng phải được bảo vệ. Bảo vệ là một cách để theo dõi chủ sở hữu của một đối tượng. Chỉ chủ sở hữu mới có thể thực hiện thay đổi hoặc xem đối tượng. Một bộ đồ chỉ có thể được Người dơi sử dụng trong các trận chiến, hoặc với Alfred để giặt.
Để duy trì sự thân thiện, một chủ đề (trong phép ẩn dụ Batman hoặc Alfred) chỉ có quyền sở hữu trong thời gian ngắn và sau đó từ bỏ quyền sở hữu. A Mutex
thường được sử dụng để theo dõi chủ sở hữu. SuitwashScheduler
callback sẽ sở hữu bộ đếm kết quả khi bộ đếm được tăng lên. Lệnh gọi lại được chạy trong SuitWashScheduler
luồng tín hiệu rằng tất cả các kết quả đã được nhận khi bộ đếm chạm mục tiêu.
Viết ví dụ bắt đầu bằng việc thiết lập một số hình cầu. Trong một ứng dụng thực, các hình cầu sẽ được thay thế bằng các thuộc tính lớp hoặc đối tượng.
$main_thread = Thread.current
$mu = Mutex.new
$count = 0
$target = 7
Quản lý và chủ sở hữu
$main_thread
và $mu
được sử dụng để quản lý các chuỗi và chờ hoàn thành kiểm tra trong khi $target
và $count
theo dõi kết quả thử nghiệm. Hãy nhớ rằng đây là một thử nghiệm nhỏ nên việc thu thập và kiểm tra kết quả phải đơn giản.
Kiểm tra được bắt đầu bằng cách tạo một phiên bản mới của SuitWashScheduler
, cung cấp cho trình khởi tạo $target
Số lần lặp lại. Trong trường hợp này, 7 bộ quần áo cần giặt. Khối được cung cấp sẽ được chạy trong SuitWashScheduler
chủ đề. Đối với mỗi lần lặp lại, $count
được tăng dần và in ra.
Nhìn về phía trước, chúng tôi nhận thấy rằng chuỗi chính, kiểm tra sẽ kiểm tra $count
cũng có nghĩa là nó sẽ cần quyền sở hữu $count
cũng như một phương tiện để chiếm quyền sở hữu $count
là cần thiết. $mu
Mutex
ví dụ là mã thông báo quyền sở hữu. Trong khối được chuyển đến SuitWashScheduler.new
gọi một $mu.synchronize
khối có quyền sở hữu đủ lâu để đặt $count
và kiểm tra kết quả. Xem thêm về kết quả trong giây lát.
SuitWashScheduler.new($target) {
$mu.synchronize {
$count += 1
puts $count
$main_thread.wakeup if $target <= $count
}
}
Kiểm tra:tất cả các bộ quần áo đã xong chưa?
Quay lại luồng chính, chúng ta cần đợi quá trình kiểm tra kết thúc. Batman cần phải đợi trước khi tất cả 7 bộ quần áo được hoàn thành. Có hai điều kiện để kiểm tra; các bài kiểm tra cập nhật $count
như mong đợi hoặc Batman cảm thấy buồn chán khi chờ đợi bài kiểm tra kết thúc và hết thời gian. Trước khi kiểm tra $count
để xem nó đã đạt đến $target
chưa , quyền sở hữu của $count
là cần thiết. Giống như trong khối cho SuitWashScheduler
một cuộc gọi tới $mu.synchronize
được sử dụng.
Nhưng điều đó không thể đúng được, nếu chúng ta khóa luồng chính thì làm sao SuitWashScheduler
chuỗi bao giờ thay đổi $count
? May mắn cho chúng tôi là có một thủ thuật gọn gàng giải quyết vấn đề này. Mutex
lớp có #sleep
phương thức từ bỏ quyền sở hữu và đợi cho đến khi hết thời gian hoặc được đánh thức. Sau khi thức dậy trong thời gian chờ hoặc #wakeup
gọi đến chuỗi chính $mu
cố gắng giành lại quyền sở hữu trước khi tiếp tục. Khi đã đạt được quyền sở hữu, kết quả có thể được kiểm tra và xác định trạng thái đạt hay không đạt của bài kiểm tra.
$mu.synchronize {
$mu.sleep($target + 1)
if $target != $count
puts 'FAILED'
else
puts 'Passed! All suits are washed and clean'
end
}
Nếu bạn muốn hiểu sâu hơn về vấn đề này, bạn có thể làm cho ví dụ thú vị hơn một chút bằng cách thử tạo nhiều bộ lập lịch và xem cách Mutex giữ $count
thay đổi do va chạm. Như thể Batman gửi một số bộ quần áo cho Alfred để giặt, và một số bộ khác cho tiệm giặt khô. Đảm bảo thay đổi logic để đảm bảo $target
séc là tổng tất cả các lợi tức mong đợi.
Làm tròn
Làm việc với các luồng và môi trường không đồng bộ trở nên dễ dàng hơn với mô hình tinh thần phù hợp. . Chúng tôi nghĩ rằng cách tóm tắt này giúp dễ hiểu và dễ nhớ hơn.
Chúng tôi hy vọng những ví dụ với sẽ khiến bạn nhớ lại cơ chế của không đồng bộ, nhưng chúng tôi hy vọng hình ảnh Người dơi cạn kiệt trong khi tất cả các bộ quần áo của anh ấy đang được giặt sẽ không gắn bó với bạn quá lâu.
P.S. Nếu bạn đã hoàn thành tất cả các phép ẩn dụ về người dơi trên blog, hãy cho chúng tôi biết.