如何等待一组goroutine中的任何*发出信号,而无需我们等待它们这样做

There are plenty of examples of how to use WaitGroup to wait for all of a group of goroutines to finish, but what if you want to wait for any one of them to finish without using a semaphore system where some process must be waiting? For example, a producer/consumer scenario where multiple producer threads add multiple entries to a data structure while a consumer is removing them one at a time. In this scenario:

  • We can't just use the standard producer/consumer semaphore system, because production:consumption is not 1:1, and also because the data structure acts as a cache, so the producers can be "free-running" instead of blocking until a consumer is "ready" to consume their product.
  • The data structure may be emptied by the consumer, in which case, the consumer wants to wait until any one of the producers finishes (meaning that there might be new things in the data structure)

Question: Is there a standard way to do that?

I've only been able to devise two methods of doing this. Both by using channels as semaphores:

var unitary_channel chan int = make(chan int, 1)

func my_goroutine() {
   // Produce, produce, produce!!!
   unitary_channel<-0 // Try to push a value to the channel
   <-unitary_channel // Remove it, in case nobody was waiting
}

func main() {
   go my_goroutine()
   go my_goroutine()
   go my_goroutine()
   for len(stuff_to_consume) { /* Consume, consume, consume */ }
   // Ran out of stuff to consume
   <-unitary_channel
   unitary_channel<-0 // To unblock the goroutine which was exiting
   // Consume more
}

Now, this simplistic example has some glaring (but solvable issues), like the fact that main() can't exit if there wasn't at least one go_routine() still running.

The second method, instead of requiring producers to remove the value they just pushed to the channel, uses select to allow the producers to exit when the channel would block them.

var empty_channel chan int = make(chan int)

func my_goroutine() {
   // Produce, produce, produce!!!
   select {
      case empty_channel <- 0: // Push if you can
      default:  // Or don't if you can't
   }
}

func main() {
   go my_goroutine()
   go my_goroutine()
   go my_goroutine()
   for len(stuff_to_consume) { /* Consume, consume, consume */ }
   // Ran out of stuff to consume
   <-unitary_channel
   // Consume more
}

Of course, this one will also block main() forever if all of the goroutines have already terminated. So, if the answer to the first question is "No, there's no standard solution to this other than the ones you've come up with", is there a compelling reason why one of these should be used instead of the other?

you could use a channel with a buffer like this

// create a channel with a buffer of 1
var Items = make(chan int, 1)
var MyArray []int

func main() {
    go addItems()
    go addItems()
    go addItems()
    go sendToChannel()
    for true {
        fmt.Println(<- Items)
    }
}

// push a number to the array
func addItems() {
    for x := 0; x < 10; x++ {
        MyArray = append(MyArray, x)
    }
}

// push to Items and pop the array
func sendToChannel() {
    for true {
        for len(MyArray) > 0 {
            Items <- MyArray[0]
            MyArray = MyArray[1:]
        }
        time.Sleep(10 * time.Second)
    }
}

the for loop in main will loop for ever and print anything that gets added to the channel and the sendToChannel function will block when the array is empty,

this way a producer will never be blocked and a consumer can consume when there are one or more items available