在Go中使用输入和输出通道

时间:2018-12-06 02:42:25

标签: go

我正在尝试使用通道在go例程之间来回传递数据。在执行过程中的某个时候,过程冻结了。我无法通过Delve确定何时或为什么。我在这到底是怎么了?

func handler(w http.ResponseWriter, r *http.Request) {
    records := liaison.Parse(r) // Parse incoming JSON into a slice.
    nRecords := len(records)
    in, out := make(chan map[string]string), make(chan map[string]string)

    // Start a go routine for each CPU core.          
    for i := 0; i < nCores; i++ {
        go clean(in, out)
    }

    // Pass each map in the slice to the input channel.
    for _, record := range records {
        in <- record
    }

    // Replace each index of the records slice with cleaned data.
    for i := 0; i < nRecords; i++ {
        cleaned := <-out
        records[i] = cleaned
    }

    close(in)
    close(out)

    liaison.Respond(w, &records)
}

func clean(in, out chan map[string]string) {
    record := <-in
    // run cleaning rules on input record.
    out <- record
}

1 个答案:

答案 0 :(得分:4)

程序使用无缓冲通道。在发送方和接收方都准备就绪之前,在无缓冲通道上的通信不会成功。

如果nRecords大于nCores,则程序将死锁:清洁程序goroutine无法发送到out,直到处理程序goroutine从out接收到。处理程序goroutine无法接收,因为它阻止发送到in

如果nRecords小于nCores,则没有工作的清洁工会在发送到out时惊慌失措。当从处理程序goroutine调用close(in)时,未完成工作的清理程序将收到<-in的零值。清洁程序goroutine将处理该零值并尝试发送到out。同时,处理程序goroutine关闭out。应用程序出现紧急情况,因为不允许在封闭的通道上发送。

这里是解决方法:

func handler(w http.ResponseWriter, r *http.Request) {
    records := liaison.Parse(r) // Parse incoming JSON into a slice.
    nRecords := len(records)
    in, out := make(chan map[string]string), make(chan map[string]string)

    // Start a go routine for each CPU core.
    for i := 0; i < nCores; i++ {
        go clean(in, out)
    }

    // Start a goroutine to feed the data. This this allows
    // the handler goroutine to continue to receiving on out.
    go func() {
        for _, record := range records {
            in <- record
        }
        // Close the channel. This causes the cleaners to exit
        // from the for / range loops.
        close(in)
    }()

    // Replace each index of the records slice with cleaned data.
    for i := 0; i < nRecords; i++ {
        cleaned := <-out
        records[i] = cleaned
    }

    liaison.Respond(w, &records)
}

func clean(in, out chan map[string]string) {
    // use for / range to read records until in is closed.
    for record := range in {
        // run cleaning rules on input record.
        out <- record
    }
}

另一种方法是用于将in更改为具有所有记录空间的缓冲通道:

func handler(w http.ResponseWriter, r *http.Request) {
    records := liaison.Parse(r) // Parse incoming JSON into a slice.
    nRecords := len(records)
    in := make(chan map[string]string, nRecords) // <-- note second argument
    out := make(chan map[string]string)

    // Start a go routine for each CPU core.
    for i := 0; i < nCores; i++ {
        go clean(in, out)
    }

    for _, record := range records {
        // this will not block because cap(in) == len(records).
        in <- record
    }
    close(in)

    // Replace each index of the records slice with cleaned data.
    for i := 0; i < nRecords; i++ {
        cleaned := <-out
        records[i] = cleaned
    }

    liaison.Respond(w, &records)
}