2013-03-02 29 views
12

Tôi vừa mới nhận thấy một hành vi đáng lo ngại. Hãy nói rằng tôi có một chương trình độc lập bao gồm một đối tượng duy nhất:Scala: Bộ sưu tập song song trong bộ khởi tạo đối tượng gây ra một chương trình treo

object ParCollectionInInitializerTest { 
    def doSomething { println("Doing something") } 

    for (i <- (1 to 2).par) { 
    println("Inside loop: " + i) 
    doSomething 
    } 

    def main(args: Array[String]) { 
    } 
} 

Chương trình này là hoàn toàn vô tội và, khi phạm vi sử dụng trong vòng lặp for là không một trường hợp tương, thực hiện đúng cách, với sản lượng sau :

Bên trong vòng lặp: 1
Làm một cái gì đó
Bên trong vòng lặp: 2
Làm một cái gì đó

Thật không may, khi sử dụng bộ sưu tập song song, chương trình chỉ bị treo mà không bao giờ gọi phương thức doSomething, vì vậy đầu ra như sau:

Bên trong vòng lặp: 2
Bên trong vòng lặp: 1

Và sau đó chương trình bị treo.
Đây có phải là lỗi khó chịu không? Tôi đang sử dụng scala-2.10.

+0

liên quan: http: // stackoverflow.com/questions/27549671/how-to-diagnose-hoặc-detect-deadlocks-in-java-static-initializers – Rich

Trả lời

15

Đây là vấn đề cố hữu có thể xảy ra trong Scala khi giải phóng tham chiếu đến đối tượng đơn lẻ trước khi quá trình xây dựng hoàn tất. Nó xảy ra do một chủ đề khác nhau cố truy cập đối tượng ParCollectionInInitializerTest trước khi nó được xây dựng hoàn chỉnh. Nó không có gì để làm với phương pháp main, thay vào đó, nó phải làm với khởi tạo đối tượng có chứa phương pháp main - hãy thử chạy điều này trong REPL, nhập vào biểu thức ParCollectionInInitializerTest và bạn sẽ nhận được kết quả tương tự. Nó cũng không liên quan gì đến các luồng công việc fork-join là các chủ đề daemon.

Đối tượng Singleton được khởi tạo một cách lười biếng. Mỗi đối tượng đơn lẻ có thể được khởi tạo chỉ một lần. Điều đó có nghĩa là luồng đầu tiên truy cập đối tượng (trong trường hợp của bạn, luồng chính) phải lấy một khóa của đối tượng, và sau đó khởi tạo nó. Mỗi luồng khác đến sau phải đợi chuỗi chính khởi tạo đối tượng và cuối cùng giải phóng khóa. Đây là cách các đối tượng singleton được thực hiện trong Scala.

Trong trường hợp của bạn, luồng công nhân bộ sưu tập song song cố gắng truy cập đối tượng đơn lẻ để gọi doSomething, nhưng không thể làm như vậy cho đến khi chuỗi chính hoàn tất khởi tạo đối tượng - vì vậy nó chờ. Mặt khác, thread chính chờ đợi trong constructor cho đến khi hoạt động song song hoàn thành, điều kiện là tất cả các thread worker hoàn thành - thread chính giữ khóa khởi tạo cho singleton mọi lúc. Do đó, một bế tắc xảy ra.

Bạn có thể gây ra hành vi này với kỳ hạn từ 2.10, hoặc với chủ đề đơn thuần, như hình dưới đây:

def execute(body: =>Unit) { 
    val t = new Thread() { 
    override def run() { 
     body 
    } 
    } 

    t.start() 
    t.join() 
} 

object ParCollection { 

    def doSomething() { println("Doing something") } 

    execute { 
    doSomething() 
    } 

} 

Dán mã này vào REPL, và sau đó viết:

scala> ParCollection 

và REPL bị treo.

+2

Giải thích tuyệt vời, cảm ơn! –

+1

Thực hiện chặn đồng thời và khởi tạo lười biếng không chơi độc đáo cùng nhau. Đây là một vấn đề chung hơn trong Scala (và Java, cho vấn đề đó). Xem SIP này: http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html – axel22

+0

Không có nghĩa là trả đũa, nhưng tôi nghĩ đây là một điều đáng ngại cho các nhà phát triển. Tôi rất biết ơn câu trả lời và nhận xét ban đầu của bạn, cực kỳ như vậy. Tôi tin rằng câu trả lời là tinh thể rõ ràng với tôi bằng cách này. – matanster

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