简体   繁体   中英

How to implement a channel and multiple readers that read the same data at the same time?

I need several functions to have the same channel as a parameter and take the same data, simultaneously. Each of these functions has an independent task from each other, but they start from the same value.

For example, given a slice of integers, one function calculates the sum of its values ​​and another calculates the average, at the same time. They would be goroutines.

One solution would be to create multiple channels from one value, but I want to avoid that. I might have to add or remove functions and for this, I would have to add or remove channels.

I think I understand that the Fan Out pattern could be an option, but I can't quite understand its implementation.

The question is against the rules of SO —as it does not present any concrete problem to be helped with but rather requests a tutoring session.

Anyway, two pointers for further research: basically—given the property of channel that each receive consumes a value sent to it, so it's impossible to read a once sent value multiple times,—such problems have two approaches to their solutions.

The first approach, which is what called a "fan-out", is to have all the consumers have a "personal" dedicated channel, copy the value to be broadcast as many times as there are consumers and send each copy to each of those dedicated channels.

The ostensibly most natural way to implement this is to have a single channel to which the producer sends its units of work—not caring how much consumers are to read them—and then have a dedicated goroutine receive those units of work, copy each of them and send the copies out to the dedicated channels of the consumers.

The second approach is to go lower level and implement basically the same scheme using stuff from the sync package.

One can think of the following scheme:

  • Have a custom struct type which has a sync.Mutex protecting the type's state.
  • Have a field which keeps the value multiple consumers have to read.
  • Have a counter in that type.
  • Have a sync.Cond in that type as well.
  • Have a channel with capacity there 1 as well.

Communicating a new value to the consumers looks like this:

  1. Lock the mutex.
  2. Verify the counter is 0, panic otherwise.
  3. Write the new value into the respective field.
  4. Set the counter to the number of consumers.
  5. Unlock the mutex.
  6. Pulse the sync.Cond .

The consumers are supposed to sleep in a wait call on that sync.Cond .
Once the sender pulses it, the goroutines running the code of consumers get woken up and try to read the value.
Reading of the value rolls like this:

  1. Lock the mutex.
  2. Verify the counter is greater than zero, panic otherwise.
  3. Read the value.
  4. Decrement the counter by one.
  5. If the counter becomes 0, send on that special channel.
  6. Unlock the mutex.

The channel is needed to communicate to the sender that all the consumers are done with their reads: before attempting to send the new value the consumer has to read from that channel.

As you can probably see, the second approach is way more involved and hard to get right, so I'd recommend to go with the first one.


I would also note that you seem to lack certain background knowledge on how to go around implementing concurrently running and communicating tasks.
I hereby recommend reading The Book and at least these chapters of The Blog :

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM