Sợi là thứ bạn có thể sẽ không bao giờ sử dụng trực tiếp trong mã cấp ứng dụng. Chúng là một nguyên thủy kiểm soát dòng chảy mà bạn có thể sử dụng để xây dựng các trừu tượng khác, mà sau đó bạn sử dụng trong mã mức cao hơn.
Có lẽ việc sử dụng # 1 các sợi trong Ruby là triển khai Enumerator
s, là một lớp Ruby cốt lõi trong Ruby 1.9. Đây là vô cùng hữu ích.
Trong Ruby 1.9, nếu bạn gọi gần như bất kỳ phương thức trình lặp nào trên các lớp lõi, mà không cần chuyển một khối, nó sẽ trả lại Enumerator
.
irb(main):001:0> [1,2,3].reverse_each
=> #<Enumerator: [1, 2, 3]:reverse_each>
irb(main):002:0> "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):003:0> 1.upto(10)
=> #<Enumerator: 1:upto(10)>
Những Enumerator
s là những đối tượng Enumerable, và each
phương pháp của họ mang lại những yếu tố mà có thể đã được mang lại bởi các phương pháp lặp ban đầu, mà nếu nó được gọi với một khối. Trong ví dụ tôi vừa đưa ra, ĐTV được trả về bởi reverse_each
có phương pháp each
có năng suất 3,2,1. Điều tra viên trả về bởi chars
sản lượng "c", "b", "a" (và vân vân). NHƯNG, không giống như các phương pháp lặp gốc, Enumerator cũng có thể trở lại các yếu tố từng người một nếu bạn gọi next
vào nó lặp đi lặp lại:
irb(main):001:0> e = "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):002:0> e.next
=> "a"
irb(main):003:0> e.next
=> "b"
irb(main):004:0> e.next
=> "c"
Bạn có thể đã nghe nói về "lặp nội bộ" và "lặp bên ngoài" (tốt mô tả của cả hai được đưa ra trong cuốn sách "Gang of Four" Design Patterns). Ví dụ trên cho thấy rằng Enumerators có thể được sử dụng để biến một iterator nội bộ thành một bên ngoài.
Đây là một cách để làm cho điều tra viên của riêng bạn:
class SomeClass
def an_iterator
# note the 'return enum_for...' pattern; it's very useful
# enum_for is an Object method
# so even for iterators which don't return an Enumerator when called
# with no block, you can easily get one by calling 'enum_for'
return enum_for(:an_iterator) if not block_given?
yield 1
yield 2
yield 3
end
end
Hãy thử nó:
e = SomeClass.new.an_iterator
e.next # => 1
e.next # => 2
e.next # => 3
Chờ một phút ... không bất cứ điều gì có vẻ kỳ lạ đó? Bạn đã viết các câu lệnh yield
trong an_iterator
dưới dạng mã đường thẳng, nhưng Điều tra viên có thể chạy chúng một tại một thời điểm. Giữa các cuộc gọi đến next
, việc thực hiện an_iterator
bị "cố định". Mỗi lần bạn gọi next
, nó sẽ tiếp tục chạy xuống theo câu lệnh yield
sau và sau đó "đóng băng" lại.
Bạn có thể đoán cách triển khai này không? Điều tra viên kết thúc cuộc gọi đến an_iterator
bằng sợi quang và chuyển một khối mà treo sợi. Vì vậy, mỗi lần an_iterator
mang lại khối, sợi mà nó đang chạy bị treo và việc thực hiện tiếp tục trên luồng chính. Lần tới, bạn gọi next
, nó chuyển quyền kiểm soát tới sợi, khối trả về và an_iterator
tiếp tục ở nơi nó bị tắt.
Sẽ có tính hướng dẫn để suy nghĩ về những gì cần thiết để thực hiện việc này mà không cần sợi. MỌI lớp mà muốn cung cấp cả bộ lặp nội bộ và bên ngoài sẽ phải chứa mã rõ ràng để theo dõi trạng thái giữa các cuộc gọi đến next
. Mỗi cuộc gọi đến tiếp theo sẽ phải kiểm tra trạng thái đó và cập nhật nó trước khi trả về một giá trị. Với sợi, chúng tôi có thể tự động chuyển đổi bất kỳ trình lặp nội bộ nào sang một bộ lặp bên ngoài.
Điều này không liên quan gì đến sợi, nhưng hãy để tôi đề cập đến một điều nữa bạn có thể làm với Điều tra viên: chúng cho phép bạn áp dụng các phương pháp đếm được bậc cao hơn cho các trình lặp khác ngoài each
. Hãy suy nghĩ về nó: thông thường tất cả các phương pháp liệt kê, bao gồm map
, select
, include?
, inject
, v.v. tất cả hoạt động trên các thành phần được cung cấp bởi each
. Nhưng nếu một đối tượng có các trình lặp khác ngoài each
thì sao?
irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ }
=> ["H"]
irb(main):002:0> "Hello".bytes.sort
=> [72, 101, 108, 108, 111]
Gọi trình vòng lặp không trả về bộ đếm, sau đó bạn có thể gọi các phương thức khác.
Lấy lại sợi, bạn đã sử dụng phương pháp take
từ Enumerable chưa?
class InfiniteSeries
include Enumerable
def each
i = 0
loop { yield(i += 1) }
end
end
Nếu có bất kỳ điều gì gọi là phương pháp each
, có vẻ như không bao giờ nên quay lại, phải không? Kiểm tra điều này:
InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Tôi không biết nếu điều này sử dụng sợi dưới mui xe, nhưng nó có thể. Sợi có thể được sử dụng để thực hiện danh sách vô hạn và đánh giá lười biếng của một loạt. Ví dụ về một số phương pháp lười biếng được xác định với Điều tra viên, tôi đã xác định một số ở đây: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Bạn cũng có thể xây dựng một cơ sở coroutine có mục đích chung sử dụng sợi. Tôi chưa bao giờ sử dụng coroutines trong bất kỳ chương trình nào của tôi, nhưng đó là một khái niệm tốt để biết.
Tôi hy vọng điều này sẽ cung cấp cho bạn một số ý tưởng về các khả năng. Như tôi đã nói lúc đầu, sợi là một nguyên thủy kiểm soát dòng chảy cấp thấp. Chúng có thể duy trì nhiều vị trí "điều khiển" trong chương trình của bạn (giống như các "dấu trang" khác nhau trong các trang của một cuốn sách) và chuyển đổi giữa chúng như mong muốn. Kể từ khi mã tùy ý có thể chạy trong một sợi, bạn có thể gọi vào mã của bên thứ ba trên một sợi, và sau đó "đóng băng" nó và tiếp tục làm một cái gì đó khác khi nó gọi lại vào mã bạn kiểm soát.
Hãy tưởng tượng một cái gì đó như thế này: bạn đang viết một chương trình máy chủ sẽ phục vụ nhiều khách hàng. Một sự tương tác hoàn chỉnh với một khách hàng liên quan đến việc đi qua một loạt các bước, nhưng mỗi kết nối là tạm thời, và bạn phải nhớ trạng thái cho mỗi máy khách giữa các kết nối. Thay vì lưu trữ rõ ràng trạng thái đó và kiểm tra trạng thái đó mỗi lần khách hàng kết nối (để xem bước "tiếp theo" mà họ phải làm là), bạn có thể duy trì chất xơ cho từng khách hàng . Sau khi xác định khách hàng, bạn sẽ lấy lại sợi của họ và khởi động lại nó. Sau đó, ở cuối mỗi kết nối, bạn sẽ đình chỉ sợi và lưu trữ nó một lần nữa. Bằng cách này, bạn có thể viết mã thẳng để thực hiện tất cả các logic cho một tương tác hoàn chỉnh, bao gồm tất cả các bước (giống như bạn tự nhiên nếu chương trình của bạn được thực hiện để chạy cục bộ).
Tôi chắc chắn có nhiều lý do tại sao một điều như vậy có thể không thực tế (ít nhất là cho bây giờ), nhưng một lần nữa tôi chỉ đang cố gắng cho bạn thấy một số khả năng. Ai biết; một khi bạn nhận được khái niệm, bạn có thể đưa ra một số ứng dụng hoàn toàn mới mà không ai khác nghĩ đến!
Ví dụ về cú pháp cũ chỉ là động cơ thúc đẩy tồi tệ nhất ;-) Thậm chí còn có công thức bạn có thể sử dụng để tính số _any_ fibonacci trong O (1). – usr
Vấn đề không phải là về thuật toán, nhưng về sự hiểu biết về sợi :) – fl00r