2012-04-16 41 views
5

Tôi đang thực hiện bài tập sử dụng kênh để triển khai hàng đợi. Cụ thể, tôi đang cố gắng sử dụng kích thước của một kênh để giới hạn số lượng goroutine đồng thời. Để wit, tôi đã viết đoạn mã sau:Cần trợ giúp để hiểu lý do tại sao chọn {} không chặn mãi mãi

package main 

import "fmt" 
import "time" 
import "math/rand" 

func runTask (t string, ch *chan bool) { 
     start := time.Now() 
     fmt.Println("starting task", t) 
     time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time 
     fmt.Println("done running task", t, "in", time.Since(start)) 
     <- *ch 
} 

func main() { 
     numWorkers := 3 
     files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} 

     activeWorkers := make(chan bool, numWorkers) 

     for _, f := range files { 
       activeWorkers <- true 
       fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers)) 
       go runTask(f, &activeWorkers) 
     } 
     select{} 
} 

Ngay bây giờ, các mã bị treo với:

throw: all goroutines are asleep - deadlock! 

kỳ vọng của tôi là cuộc gọi đến chọn sẽ chặn vĩnh viễn và để cho các goroutines chấm dứt mà không có một bế tắc.

Vì vậy, tôi có một câu hỏi hai lần: tại sao không chọn chặn mãi mãi và, ngắn ném trong một thời gian.Sleep() gọi sau vòng lặp, làm thế nào tôi có thể tránh deadlocks?

Chúc mừng,

-mtw

Trả lời

6

Arlen Cuss đã viết một câu trả lời hay. Tôi chỉ muốn đề xuất một thiết kế khác cho hàng đợi công việc của bạn. Thay vì giới hạn số lượng mục nhập kênh của bạn có thể đệm, bạn cũng có thể chỉ sinh ra một số lượng giới hạn các goroutines công nhân cảm thấy tự nhiên hơn. Một cái gì đó như thế:

package main 

import "fmt" 
import "time" 
import "math/rand" 

func runTask(t string) string { 
    start := time.Now() 
    fmt.Println("starting task", t) 
    time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time 
    fmt.Println("done running task", t, "in", time.Since(start)) 
    return t 
} 

func worker(in chan string, out chan string) { 
    for t := range in { 
     out <- runTask(t) 
    } 
} 

func main() { 
    numWorkers := 3 

    // spawn workers 
    in, out := make(chan string), make(chan string) 
    for i := 0; i < numWorkers; i++ { 
     go worker(in, out) 
    } 

    files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} 

    // schedule tasks 
    go func() { 
     for _, f := range files { 
      in <- f 
     } 
    }() 

    // get results 
    for _ = range files { 
     <-out 
    } 
} 

Bạn cũng có thể sử dụng một sync.WaitGroup nếu bạn chỉ muốn đợi cho đến khi tất cả các nhiệm vụ đã được thực hiện, nhưng sử dụng một kênh out có ưu điểm là bạn có thể tổng hợp các kết quả sau đó. Ví dụ: nếu mỗi tác vụ trả về số lượng từ trong tệp đó, vòng lặp cuối cùng có thể được sử dụng để tổng hợp tất cả các từ riêng lẻ.

4

Thứ nhất, bạn không cần phải vượt qua một con trỏ đến kênh; các kênh, như bản đồ và các kênh khác, are references, nghĩa là dữ liệu cơ bản không được sao chép, chỉ là con trỏ đến dữ liệu thực tế. Nếu bạn cần một con trỏ đến số chan chính nó, bạn sẽ biết khi nào thời gian đó đến.

Sự cố xảy ra do chương trình bị rơi vào trạng thái mà mọi goroutine bị chặn. Điều này nên là không thể; nếu mỗi goroutine bị chặn, thì không quá trình nào có thể đến và đánh thức một goroutine khác (và chương trình của bạn sẽ bị treo).

Goroutine chính gió lên trong một select {} —không phải chờ đợi bất kỳ ai, chỉ cần treo. Sau khi kết thúc goroutine runTask cuối cùng, chỉ còn lại goroutine chính và nó đang chờ đợi không có ai.

Bạn cần thêm một số cách để biết khi nào mọi goroutine đã kết thúc; có lẽ một kênh khác có thể nhận được sự kiện kết thúc.

Đây là một chút xấu xí, nhưng có thể là một số nguồn cảm hứng.

package main 

import "fmt" 
import "time" 
import "math/rand" 

func runTask(t string, ch chan bool, finishedCh chan bool) { 
    start := time.Now() 
    fmt.Println("starting task", t) 
    time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time 
    fmt.Println("done running task", t, "in", time.Since(start)) 
    <-ch 
    finishedCh <- true 
} 

func main() { 
    numWorkers := 3 
    files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} 

    activeWorkers := make(chan bool, numWorkers) 
    finishedWorkers := make(chan bool) 
    done := make(chan bool) 

    go func() { 
     remaining := len(files) 
     for remaining > 0 { 
      <-finishedWorkers 
      remaining -= 1 
     } 

     done <- true 
    }() 

    for _, f := range files { 
     activeWorkers <- true 
     fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers)) 
     go runTask(f, activeWorkers, finishedWorkers) 
    } 

    <-done 
} 
+0

Tôi sẽ cẩn thận với điều đó; tất cả mọi thứ trong đi được truyền theo giá trị. Bản đồ và lát thực sự được sao chép, tuy nhiên một phần dữ liệu được sao chép bao gồm các con trỏ đến dữ liệu cơ bản. http://golang.org/doc/faq#pass_by_value. Đó là một sự phân biệt tinh tế nhưng quan trọng; lát có các dữ liệu khác không phải là con trỏ và không được chia sẻ. – Greg

+0

@Greg: "Bản đồ và lát thực sự được sao chép". Tách tóc, tôi nghĩ vậy. Theo tài liệu bạn đã liên kết: "Các giá trị bản đồ và slice hoạt động như con trỏ ... Sao chép một giá trị bản đồ hoặc lát không sao chép dữ liệu mà nó trỏ đến." Thuật ngữ của tôi không phải là bước với các tài liệu Go sử dụng bây giờ, nhưng ngữ nghĩa phần lớn là tương đương. – Ashe

+0

@Greg: đã dọn dẹp văn xuôi ở đó một chút. – Ashe

1

tux21b đã đăng một giải pháp thành ngữ hơn, nhưng tôi muốn trả lời câu hỏi của bạn theo một cách khác. chọn {} sẽ chặn vĩnh viễn, vâng. Một bế tắc xảy ra khi tất cả các goroutine bị chặn. Nếu tất cả các goroutines khác của bạn kết thúc, thì bạn chỉ còn lại goroutine bị khóa, đó là một bế tắc.

Thông thường, bạn muốn làm điều gì đó trong goroutine chính của bạn sau khi tất cả những người khác đã hoàn thành, hoặc bằng cách sử dụng kết quả của họ, hoặc chỉ làm sạch, và cho rằng bạn muốn làm những gì tux21b đề nghị.Nếu bạn thực sự chỉ muốn chính kết thúc và để phần còn lại của goroutines làm công việc của họ, hãy đặt defer runtime.Goexit() ở đầu chức năng chính của bạn. Điều này sẽ làm cho nó thoát ra mà không cần thoát khỏi chương trình.

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