通过通道将值发送到多个go例程

时间:2018-04-19 04:50:17

标签: go

我想在一个频道中发送一个值来从主函数中转到例程。发生的事情是哪个例程将首先从通道接收值。

package main

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

func main() {
    var ch chan int
    ch = make(chan int)
    ch <- 1
    receive(ch)
}

func receive(ch chan int){
    for i := 0; i < 4; i++ {
        // Create some threads
        go func(i int) {
            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
            fmt.Println(<-ch)
        }(i)
    }
}

我目前的实施是错误的。

  

致命错误:所有goroutine都睡着了 - 死锁!

我怎么知道哪个例程将首先从频道接收值。其他go例程会发生什么情况如果那些会运行或抛出错误,因为没有通道可以接收该值。因为其中一个人已经收到了。

如果创建一个缓冲通道我的代码可以工作。因此,我不知道在创建如下所示的缓冲通道时,它在场景背后发生了什么:

func main() {
    var ch chan int
    ch = make(chan int, 10)
    ch <- 1
    receive(ch)
}

如果我们看下面的代码。我可以看到我们可以直接通过通道发送值,不需要创建一个go例程来通过一个通道向另一个例程发送一个值。

package main

import "fmt"

func main() {

    // We'll iterate over 2 values in the `queue` channel.
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)

    for elem := range queue {
        fmt.Println(elem)
    }
}

然后我的代码出了什么问题。为什么会造成僵局。

2 个答案:

答案 0 :(得分:1)

无缓冲通道(没有长度)阻塞,直到收到该值。这意味着写入通道的程序将在写入通道后停止,直到读取它为止。如果在主线程中发生这种情况,则在调用receive之前,会导致死锁。

还有两个问题:您需要使用WaitGroup暂停完成直到完成,并且通道的行为类似于并发队列。特别是,它具有推送和弹出操作,这两个操作都使用<-执行。例如:

//Push to channel, channel contains 1 unless other things were there
c <- 1
//Pop from channel, channel is empty
x := <-c

这是一个有效的例子:

package main

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

func main() {
        var ch chan int 
        ch = make(chan int)
        go func() {
                ch <- 1
                ch <- 1
                ch <- 1
                ch <- 1
        }() 
        receive(ch)
}

func receive(ch chan int) {
        wg := &sync.WaitGroup{}
        for i := 0; i < 4; i++ {
                // Create some threads
                wg.Add(1)
                go func(i int) {
                        time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
                        fmt.Println(<-ch)
                        wg.Done()
                }(i)
        }   
        wg.Wait()
        fmt.Println("done waiting")
}

Playground Link

正如您所看到的,WaitGroup也很简单。您在更高的范围内声明它。它本质上是一个花哨的计数器,有三种主要方法。当您调用wg.Add(1)时,计数器会增加,当您调用wg.Done()时,计数器会减少,当您调用wg.Wait()时,执行将暂停,直到计数器达到0。

答案 1 :(得分:1)

如果只需要启动多个工作人员并将任务发送到任何,那么在向发送值之前,最好先运行工作人员频道,因为正如@mkopriva上面所说,写一个频道是一个阻塞操作。您总是需要有消费者,否则执行将冻结。

func main() {
    var ch chan int
    ch = make(chan int)

    receive(ch)

    ch <- 1
}

func receive(ch chan int) {
    for i := 0; i < 4; i++ {
        // Create some threads
        go func(i int) {
            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
            fmt.Printf("Worker no %d is processing the value %d\n", i, <-ch)
        }(i)
    }
}

问题的简短回答&#34;哪个常规会收到它?&#34; - 无论。 :)其中任何一个,你都无法肯定地说。

但是我不知道什么是time.Sleep(...),保持原样。