2015-03-16 15 views
41

Tôi đã dành thời gian tìm hiểu chủ đề của bối cảnh thực thi Scala, mô hình luồng cơ bản và đồng thời. Bạn có thể giải thích theo cách nào không scala.concurrent.blocking"điều chỉnh hành vi thời gian chạy""có thể cải thiện hiệu suất hoặc tránh deadlocks" như được mô tả trong scaladoc?scala.concurrent.blocking - nó thực sự làm gì?

Trong the documentation, nó được trình bày như một phương tiện để chờ api không triển khai awaitable. (Có lẽ cũng chỉ cần chạy tính toán dài nên được bao bọc?).

Nó thực sự là gì?

Following through the source không dễ dàng phản bội bí mật của nó.

Trả lời

60

blocking có nghĩa là hoạt động như một gợi ý cho mã ExecutionContext rằng mã bị chặn đang chặn và có thể dẫn đến sự cố đói. Điều này sẽ cung cấp cho các hồ bơi thread một cơ hội để đẻ trứng chủ đề mới để ngăn chặn nạn đói. Đây là ý nghĩa của "điều chỉnh hành vi thời gian chạy". Nó không phải là ma thuật mặc dù, và sẽ không làm việc với mọi ExecutionContext.

Hãy xem xét ví dụ sau:

import scala.concurrent._ 
val ec = scala.concurrent.ExecutionContext.Implicits.global 

(0 to 100) foreach { n => 
    Future { 
     println("starting Future: " + n) 
     blocking { Thread.sleep(3000) } 
     println("ending Future: " + n) 
    }(ec) 
} 

này được sử dụng mặc định toàn cầu ExecutionContext. Chạy mã này, bạn sẽ nhận thấy rằng tất cả 100 Future s đều được thực hiện ngay lập tức, nhưng nếu bạn xóa blocking, chúng chỉ thực thi một vài lần tại một thời điểm. Mặc định ExecutionContext sẽ phản ứng để chặn các cuộc gọi (được đánh dấu như vậy) bằng cách sinh ra các luồng mới, và do đó không bị quá tải khi chạy Future s.

Bây giờ nhìn vào ví dụ này với một hồ bơi cố định của 4 chủ đề:

import java.util.concurrent.Executors 
val executorService = Executors.newFixedThreadPool(4) 
val ec = ExecutionContext.fromExecutorService(executorService) 

(0 to 100) foreach { n => 
    Future { 
     println("starting Future: " + n) 
     blocking { Thread.sleep(3000) } 
     println("ending Future: " + n) 
    }(ec) 
} 

ExecutionContext này không được xây dựng để xử lý sinh sản chủ đề mới, và vì vậy ngay cả với mã chặn tôi bao quanh với blocking, bạn có thể nhìn thấy rằng nó sẽ vẫn chỉ thực hiện tối đa 4 Future s tại một thời điểm. Và vì vậy đó là lý do tại sao chúng tôi nói nó "có thể cải thiện hiệu suất hoặc tránh deadlocks" - nó không được bảo đảm. Như chúng ta thấy ở sau ExecutionContext, nó không được đảm bảo chút nào.

Tính năng này hoạt động như thế nào? Như liên kết, blocking thực thi mã này:

BlockContext.current.blockOn(body)(scala.concurrent.AwaitPermission) 

BlockContext.current lấy BlockContext từ thread hiện tại, nhìn thấy here. A BlockContext thường chỉ là một Thread với đặc tính BlockContext được trộn lẫn. Như được thấy trong nguồn, nó được lưu trữ trong một ThreadLocal hoặc nếu không tìm thấy ở đó, thì đó là mẫu khớp với chuỗi hiện tại. Nếu chuỗi hiện tại không phải là BlockContext, thì thay vào đó, DefaultBlockContext sẽ được sử dụng.

Tiếp theo, blockOn được gọi là BlockContext hiện tại. blockOn là một phương pháp trừu tượng trong BlockContext, do đó, việc triển khai phụ thuộc vào cách ExecutionContext xử lý nó. Nếu chúng ta nhìn vào implementation for DefaultBlockContext (khi luồng hiện tại không phải là BlockContext), chúng ta thấy rằng blockOn thực sự không có gì ở đó.Vì vậy, sử dụng blocking trong một không BlockContext có nghĩa là không có gì đặc biệt được thực hiện ở tất cả, và mã được chạy như là, không có tác dụng phụ.

Còn chủ đề là BlockContext thì sao? Ví dụ: trong ngữ cảnh global, hãy xem here, blockOn hoạt động tốt hơn một chút. Đào sâu hơn, bạn có thể thấy rằng nó đang sử dụng ForkJoinPool dưới mui xe, với DefaultThreadFactory được xác định trong cùng một đoạn mã được sử dụng để sinh ra các chuỗi mới trong ForkJoinPool. Nếu không triển khai blockOn từ số BlockContext (chuỗi), thì ForkJoinPool không biết bạn đang chặn và sẽ không cố gắng sinh ra nhiều chuỗi hơn để phản hồi.

Scala's Await cũng vậy, sử dụng blocking để triển khai.

+0

Dường như điều này hoàn toàn là ủy quyền cho phép thuật của Java ['util.concurrent.ForkJoinPool's'] (http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinPool.ManagedBlocker .html) tùy ý, ngoài một số mã scala không được chấp nhận. Tôi tự hỏi sau đó, làm thế nào để ngã ba của Java tham gia thực hiện sau đó quyết định liệu/khi để đẻ trứng một chủ đề mới. Trạng thái luồng của Java có vẻ hơi kém [không đủ] (http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.State.html#BLOCKED) làm tiêu chí, trừ khi _any chặn code_ sẽ liên quan đến khóa màn hình. – matanster

+0

Đối với cách ForkJoinPool quyết định, bạn có thể tìm thấy câu trả lời trong [source] (http://docjar.com/html/api/java/util/concurrent/ForkJoinPool.java.html) một chút ít hơn thỏa mãn. Mọi thứ bắt đầu có mức độ rất thấp. 'addWorker' có thể tạo các luồng mới từ' ForkJoinWorkerThreadFactory', được gọi ở hai nơi: 'signalWork' và' tryPreBlock'. Cả hai đều chứa một số lượng thú vị của logic bitwise. –

+0

Hãy để chúng tôi để một câu hỏi riêng biệt (hoặc cuốn sách) ... cho đến khi được hiển thị khác, tôi cho rằng hầu hết các api chặn có thể đủ khả năng phát hiện bởi thực hiện Java 7 đó, ngoại trừ một nhiệm vụ chuyên sâu cpu dài có thể được coi là "chặn" trong chế độ xem "Ứng dụng phản hồi" của TypeSafe. – matanster

Các vấn đề liên quan