2012-06-20 29 views
20

Tôi muốn nghe thường xuyên trên hai kênh, bị chặn khi cả hai kênh đều bị cạn kiệt. Tuy nhiên, nếu cả hai kênh đều chứa dữ liệu, tôi muốn một kênh bị cạn kiệt trước khi kênh kia được xử lý.Mức độ ưu tiên trong cách giải quyết lựa chọn Go

Trong ví dụ hoạt động bên dưới, tôi muốn tất cả out được tháo ra trước khi exit được xử lý. Tôi sử dụng một mã số select không có bất kỳ thứ tự ưu tiên nào. Làm thế nào tôi có thể nhận được xung quanh vấn đề, làm cho tất cả 10 out-giá trị được xử lý trước khi xuất cảnh?

package main 

import "fmt" 

func sender(out chan int, exit chan bool){ 
    for i := 1; i <= 10; i++ { 
     out <- i 
    } 
    exit <- true 
} 

func main(){ 
    out := make(chan int, 10) 
    exit := make(chan bool) 

    go sender(out, exit) 

    L: 
    for { 
     select { 
      case i := <-out: 
       fmt.Printf("Value: %d\n", i) 
      case <-exit: 
       fmt.Println("Exiting") 
       break L 
     } 
    } 
    fmt.Println("Did we get all 10? Most likely not") 
} 

Trả lời

14
package main 

import "fmt" 

func sender(out chan int, exit chan bool) { 
    for i := 1; i <= 10; i++ { 
     out <- i 
    } 
    exit <- true 
} 

func main() { 
    out := make(chan int, 10) 
    exit := make(chan bool) 

    go sender(out, exit) 

    for { 
     select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
      continue 
     default: 
     } 
     select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
      continue 
     case <-exit: 
      fmt.Println("Exiting") 
     } 
     break 
    } 
    fmt.Println("Did we get all 10? I think so!") 
} 

Trường hợp mặc định của lựa chọn đầu tiên sẽ không chặn. Lựa chọn sẽ thoát ra khỏi kênh mà không cần nhìn vào kênh thoát, nhưng nếu không sẽ không chờ đợi. Nếu kênh đầu ra trống, nó ngay lập tức giảm xuống lựa chọn thứ hai. Lựa chọn thứ hai đang chặn. Nó sẽ chờ dữ liệu trên một trong hai kênh. Nếu một lối ra đến, nó sẽ xử lý nó và cho phép vòng lặp thoát ra. Nếu dữ liệu đến, nó sẽ sao lưu phần đầu của vòng lặp và quay trở lại chế độ thoát nước.

+1

Ý tưởng rất giống với ý tưởng của tôi. Nhưng đúng, với câu lệnh 'continue', bạn sẽ loại bỏ sự cần thiết của một lá cờ. Thông minh. Vâng, điều này có lẽ là một câu trả lời tốt như tôi có thể giả định để có được. Cảm ơn! – ANisus

+2

điều này sẽ lặp vô hạn trong câu lệnh chọn đầu tiên nếu kênh ngoài bị đóng. – jorelli

+1

jorelli, khá đúng. Nếu bạn muốn cho phép goroutines thù địch hoặc bị lỗi đóng kênh bất ngờ, bạn sẽ kiểm tra trạng thái ok khi nhận. – Sonia

5

Một cách tiếp cận:

package main 

import "fmt" 

func sender(c chan int) chan int { 
     go func() { 
       for i := 1; i <= 15; i++ { 
         c <- i 
       } 
       close(c) 
     }() 
     return c 
} 

func main() { 
     for i := range sender(make(chan int, 10)) { 
       fmt.Printf("Value: %d\n", i) 
     } 
     fmt.Println("Did we get all 15? Surely yes") 
} 

$ go run main.go 
Value: 1 
Value: 2 
Value: 3 
Value: 4 
Value: 5 
Value: 6 
Value: 7 
Value: 8 
Value: 9 
Value: 10 
Value: 11 
Value: 12 
Value: 13 
Value: 14 
Value: 15 
Did we get all 15? Surely yes 
$ 
+1

Cảm ơn đã gợi ý! Nếu tôi hiểu bạn một cách chính xác, bạn chỉ nên sử dụng một kênh, gọi một lối ra bằng cách đóng kênh, do đó phá vỡ câu lệnh 'for range'. Đúng, có lẽ đó là một cách tốt hơn để làm điều đó, nhưng trong trường hợp của tôi, tôi đang làm việc với hai kênh. – ANisus

1

Tôi đã tạo ra một cách giải quyết khá đơn giản. Nó làm những gì tôi muốn, nhưng nếu bất cứ ai khác có một giải pháp tốt hơn, xin vui lòng cho tôi biết:

exiting := false 
for !exiting || len(out)>0 { 
    select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
     case <-exit: 
      exiting = true 
      fmt.Println("Exiting") 
    } 
} 

Thay vì thoát về tiếp nhận, tôi cờ một lối ra, thoát khỏi một khi tôi đã thực hiện chắc chắn không có gì còn lại trong chan out .

+1

Công trình này đẹp và nhỏ gọn, nhưng sử dụng một số thủ thuật bạn nên cố gắng tránh nói chung. Cờ trở nên khó hiểu khi các chương trình trở nên lớn hơn. Họ là loại giống như gotos. Nghiêm túc hơn, len (chan) thường có thể giới thiệu các cuộc đua. Nó có vẻ ổn trong tình huống này, nhưng trong nhiều trường hợp nó không hợp lệ để đưa ra quyết định dựa trên len (chan) bởi vì nó có thể thay đổi trước khi bạn hành động. Hãy tưởng tượng trường hợp bạn nhận được len == 0, sau đó một giá trị đến, sau đó một lối ra đến, và chọn chọn lối ra. Bạn có thể nhún vai và nói rằng họ đến cùng một lúc, nhưng trong một số thời gian chương trình quan trọng, nó có thể quan trọng. – Sonia

+0

Umm, có thể nó vẫn hoạt động trong trường hợp tôi mô tả. Xin lỗi nếu đó là một ví dụ xấu. Nhưng dù sao, tôi cố gắng tránh sử dụng len trong mã đồng bộ hóa. – Sonia

+0

Xin chào một lần nữa Sonia :). Đầu vào tốt. Có, trong trường hợp của tôi nó không quan trọng nhiều. Tôi chỉ muốn tuôn ra những gì đang diễn ra trước khi xuất cảnh. Tuy nhiên, tôi thực sự redid mã bằng cách sử dụng 'cho phạm vi' và' close (out) 'thay vào đó (như đề xuất bởi jmnl). Sau đó, chỉ các sự kiện ngoài được đặt trong ống kênh trước khi đóng sẽ bị "xả". Tôi sẽ tránh quyết định dựa trên len (chan) nếu Nasdaq từng yêu cầu tôi làm một số chương trình Go cho họ;) – ANisus

26

ngôn ngữ hỗ trợ bản gốc này và không cần giải pháp thay thế. Nó rất đơn giản: kênh bỏ thuốc chỉ nên hiển thị cho nhà sản xuất. Khi bỏ thuốc lá, nhà sản xuất đóng kênh. Chỉ khi kênh trống và đóng mà người tiêu dùng đã bỏ. Này được thực hiện bằng cách đọc từ kênh như sau:

v, ok := <-c 

này sẽ đặt ok để một boolean chỉ ra hay không giá trị v đã thực sự đọc ra khỏi kênh (ok == true), hoặc nếu v được thành lập vào giá trị 0 của loại được xử lý bởi kênh cc bị đóng và trống (ok == false). Khi kênh bị đóng và không trống, v sẽ là giá trị hợp lệ và ok sẽ là true. Khi kênh được đóng và để trống, v sẽ là giá trị 0 của loại được xử lý bởi kênh cok sẽ là false, cho biết rằng v là vô ích.

Dưới đây là một ví dụ để minh họa:

package main 

import (
    "fmt" 
    "math/rand" 
    "time" 
) 

var (
    produced = 0 
    processed = 0 
) 

func produceEndlessly(out chan int, quit chan bool) { 
    defer close(out) 
    for { 
     select { 
     case <-quit: 
      fmt.Println("RECV QUIT") 
      return 
     default: 
      out <- rand.Int() 
      time.Sleep(time.Duration(rand.Int63n(5e6))) 
      produced++ 
     } 
    } 
} 

func quitRandomly(quit chan bool) { 
    d := time.Duration(rand.Int63n(5e9)) 
    fmt.Println("SLEEP", d) 
    time.Sleep(d) 
    fmt.Println("SEND QUIT") 
    quit <- true 
} 

func main() { 
    vals, quit := make(chan int, 10), make(chan bool) 
    go produceEndlessly(vals, quit) 
    go quitRandomly(quit) 
    for { 
     x, ok := <-vals 
     if !ok { 
      break 
     } 
     fmt.Println(x) 
     processed++ 
     time.Sleep(time.Duration(rand.Int63n(5e8))) 
    } 
    fmt.Println("Produced:", produced) 
    fmt.Println("Processed:", processed) 
} 

này được diễn tả trong "Nhận điều hành" của đi spec: http://golang.org/ref/spec#Receive_operator

+0

Cảm ơn đây chính xác là giải pháp mà tôi đang tìm kiếm, và nó không có lỗi điều kiện chủng tộc tiềm ẩn trong câu trả lời của Sonia – BrandonAGr

0

Trong trường hợp của tôi, tôi thực sự muốn ưu tiên dữ liệu từ một trên kênh khác và không chỉ có tín hiệu thoát ra ngoài băng tần.Vì lợi ích của bất cứ ai khác với cùng một vấn đề tôi nghĩ rằng phương pháp này hoạt động mà không điều kiện cuộc đua tiềm năng:

OUTER: 
for channelA != nil || channelB != nil { 

    select { 

    case typeA, ok := <-channelA: 
     if !ok { 
      channelA = nil 
      continue OUTER 
     } 
     doSomething(typeA) 

    case nodeIn, ok := <-channelB: 
     if !ok { 
      channelB = nil 
      continue OUTER 
     } 

     // Looped non-blocking nested select here checks that channelA 
     // really is drained before we deal with the data from channelB 
     NESTED: 
     for { 
      select { 
      case typeA, ok := <-channelA: 
       if !ok { 
        channelA = nil 
        continue NESTED 
       } 
       doSomething(typeA) 

      default: 
       // We are free to process the typeB data now 
       doSomethingElse(typeB) 
       break NESTED 
      } 
     } 
    } 

} 
0

Tôi nghĩ rằng câu trả lời Sonia là incorrect.This là giải pháp của tôi, một chút phức tạp.

package main 

import "fmt" 

func sender(out chan int, exit chan bool){ 
    for i := 1; i <= 10; i++ { 
     out <- i 
    } 
    exit <- true 
} 

func main(){ 
    out := make(chan int, 10) 
    exit := make(chan bool) 

    go sender(out, exit) 

    L: 
    for { 
     select { 
      case i := <-out: 
       fmt.Printf("Value: %d\n", i) 
      case <-exit: 
       for{ 
        select{ 
        case i:=<-out: 
         fmt.Printf("Value: %d\n", i) 
        default: 
         fmt.Println("Exiting") 
         break L 
        } 
       } 
       fmt.Println("Exiting") 
       break L 
     } 
    } 
    fmt.Println("Did we get all 10? Yes!") 
} 
0

Có lý do cụ thể nào khi sử dụng kênh đệm make(chan int, 10) không?

Bạn cần sử dụng kênh không được hỗ trợ so với bộ đệm mà bạn đang sử dụng.

Chỉ cần xóa 10, nó chỉ là make(chan int).

Bằng cách này thực hiện trong sender chức năng chỉ có thể tiến hành các exit <- true tuyên bố sau những tin nhắn cuối cùng từ các kênh out được dequeued bởi tuyên bố i := <-out. Nếu câu lệnh đó chưa được thực hiện, không có cách nào để có thể đạt được exit <- true trong goroutine.

0

Đây là một tùy chọn khác. Mã

Tiêu Dùng:

go func() { 
    stop := false 
    for { 
     select { 
     case item, _ := <-r.queue: 
     doWork(item) 
     case <-r.stopping: 
     stop = true 
     } 
     if stop && len(r.queue) == 0 { 
     break 
     } 
    } 
    }() 
Các vấn đề liên quan