2009-08-21 50 views
16

Trong mã bên dưới, tôi tạo 20 chủ đề, yêu cầu mỗi người in ra một tin nhắn, ngủ và in một tin nhắn khác. Tôi bắt đầu các chủ đề trong chủ đề chính của tôi và sau đó tham gia tất cả các chủ đề là tốt. Tôi sẽ mong đợi thông báo "tất cả đã thực hiện" chỉ được in sau khi tất cả các chủ đề đã hoàn thành. Tuy nhiên, "tất cả được thực hiện" được in trước khi tất cả các chủ đề được thực hiện. Ai đó có thể giúp tôi hiểu hành vi này?Thread.join không hoạt động như tôi mong đợi trong scala

Cảm ơn. Kent

Đây là mã:

def ttest() = { 
    val threads = 
     for (i <- 1 to 5) 
     yield new Thread() { 
      override def run() { 
      println("going to sleep") 
      Thread.sleep(1000) 
      println("awake now") 
      } 
     } 

    threads.foreach(t => t.start()) 
    threads.foreach(t => t.join()) 
    println("all done") 
    } 

Đây là kết quả:

going to sleep 
all done 
going to sleep 
going to sleep 
going to sleep 
going to sleep 
awake now 
awake now 
awake now 
awake now 
awake now 
+0

Tôi đang upvoting câu hỏi này vì tương tác cụ thể của việc hiểu, phạm vi và chủ đề có vẻ là một mẫu lỗi lặp lại phổ biến. –

Trả lời

7

Trong code của bạn, threads được hoãn lại - mỗi khi bạn lặp nó, khái niệm for máy phát điện chạy một lần nữa. Do đó, bạn thực sự tạo 10 luồng ở đó - foreach đầu tiên tạo 5 và bắt đầu chúng, số thứ hai foreach tạo thêm 5 chuỗi nữa (không bắt đầu) và tham gia - vì chúng không chạy, join trả về ngay lập tức. Bạn nên sử dụng toList trên kết quả của for để tạo ảnh chụp ổn định.

+0

Cảm ơn, Pavel. Đó là chính xác nó. Tôi không biết cách xóa câu trả lời không thực sự ở trên. – Kent

11

Nó hoạt động nếu bạn chuyển đổi Range thành một List:

def ttest() = { 
    val threads = 
     for (i <- 1 to 5 toList) 
     yield new Thread() { 
      override def run() { 
      println("going to sleep") 
      Thread.sleep(1000) 
      println("awake now") 
      } 
     } 

    threads.foreach(t => t.start()) 
    threads.foreach(t => t.join()) 
    println("all done") 
    } 

Vấn đề là "1 to 5" là một Range, và phạm vi không phải là "nghiêm ngặt", do đó để nói chuyện. Trong tiếng Anh tốt, khi bạn gọi phương thức map trên Range, nó không tính toán từng giá trị ngay sau đó. Thay vào đó, nó tạo ra một đối tượng - một RandomAccessSeq.Projection trên Scala 2.7 - có tham chiếu đến hàm được truyền tới bản đồ và một hàm khác đến phạm vi ban đầu. Do đó, khi bạn sử dụng một phần tử của phạm vi kết quả, hàm bạn đã chuyển tới bản đồ được áp dụng cho phần tử tương ứng của phạm vi ban đầu. Và điều này sẽ xảy ra mỗi khi bạn truy cập vào bất kỳ phần tử nào trong phạm vi kết quả.

Điều này có nghĩa là mỗi lần bạn tham chiếu đến phần tử t, bạn sẽ gọi số new Thread() { ... } một lần nữa. Vì bạn làm điều đó hai lần, và phạm vi có 5 phần tử, bạn đang tạo 10 luồng. Bạn bắt đầu vào 5 đầu tiên, và tham gia vào các thứ hai 5.

Nếu đây là khó hiểu, nhìn vào ví dụ dưới đây:

scala> object test { 
    | val t = for (i <- 1 to 5) yield { println("Called again! "+i); i } 
    | } 
defined module test 

scala> test.t 
Called again! 1 
Called again! 2 
Called again! 3 
Called again! 4 
Called again! 5 
res4: scala.collection.generic.VectorView[Int,Vector[_]] = RangeM(1, 2, 3, 4, 5) 

scala> test.t 
Called again! 1 
Called again! 2 
Called again! 3 
Called again! 4 
Called again! 5 
res5: scala.collection.generic.VectorView[Int,Vector[_]] = RangeM(1, 2, 3, 4, 5) 

Mỗi lần tôi in t (bằng cách Scala REPL in res4res5), biểu thức mang lại được đánh giá lại. Nó xảy ra cho các yếu tố cá nhân quá:

scala> test.t(1) 
Called again! 2 
res6: Int = 2 

scala> test.t(1) 
Called again! 2 
res7: Int = 2 

EDIT

Tính đến Scala 2.8, Range sẽ nghiêm ngặt, vì vậy các mã trong câu hỏi sẽ làm việc như dự kiến ​​ban đầu.

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