On Tue, Sep 2, 2025 at 11:59 AM robert engels <reng...@ix.netcom.com> wrote:

> I don’t think this is correct. There is only a single select on the
> consumer side - the order of sends by the producers is already random based
> on go routine wakeup/scheduling.
>
> On Sep 2, 2025, at 10:46, Jason E. Aten <j.e.a...@gmail.com> wrote:
>
> Yes, but not in terms of performance. Using a buffered
> channel could provide more "fairness" in the sense of "first-come, first
> served".
>
> If you depend on the (pseudo-randomized) select to decide on which
> producer's job gets serviced next, you could increase your response
> latency by arbitrarily delaying an early job for.a long time, while a
> late arriving job can "jump the queue" and get serviced immediately.
>
> The buffered channel will preserve some of the arrival order. But--this
> is only up to its length--after that the late arrivers will still be
> randomly
> serviced--due to the pseudo random select mechanism. So if you
> demand true FIFO for all jobs, then you might well be better served
> by using a mutex and and a slice to keep your jobs anyway--such
> that the limit on your queue is available memory rather than a
> fixed channel buffer size.
>
> I don't think channel receive order is random when the senders are blocked.
Sending goroutines are queued in a linked list in FIFO order within the
runtime's channel struct (hchan)
<https://cs.opensource.google/go/go/+/master:src/runtime/chan.go;l=45;drc=a8564bd412d4495a6048f981d30d4d7abb1e45a7>
(different cases of a select are selected at random for fairness, though)

I would recommend using a buffered channel with size 1 for any
response-channel so the worker-goroutine
doesn't have to wait for another goroutine to schedule in order to pick up
the next work in the queue.

>
> Of course if you over-run all of your memory, you are in trouble
> again. As usual, back-pressure is a really critical component
> of most designs. You usually want it.
>
>
Back-pressure is definitely helpful in cases like this.

>
> On Tuesday, September 2, 2025 at 4:33:05 PM UTC+1 Egor Ponomarev wrote:
>
>> Hi Robert, Jason,
>>
>> Thank you both for your detailed and thoughtful responses — they helped
>> me see the problem more clearly. Let me share some more details about our
>> specific case:
>>
>>    -
>>
>>    We have exactly one consumer (worker), and we can’t add more because
>>    the underlying resource can only be accessed by one process at a time
>>    (think of it as exclusive access to a single connection).
>>    -
>>
>>    The worker operation is a TCP connection, which is usually fast, but
>>    the network can occasionally be unreliable and introduce delays.
>>    -
>>
>>    We may have lots of producers, and each producer waits for a result
>>    after submitting a request.
>>
>> Given these constraints, can an unbuffered channel have any advantage
>> over a buffered one for our case?
>> My understanding is that producers will just end up blocking when the
>> single worker can’t keep up — so whether the blocking happens at “enqueue
>> time” (unbuffered channel) or later (buffered channel).
>>
>> What’s your view — is there any benefit in using an unbuffered/buffered
>> channel in this situation?
>>
>> Thanks again for the guidance!
>>
>> понедельник, 1 сентября 2025 г. в 14:04:48 UTC-5, Jason E. Aten:
>>
>>> Hi Egor,
>>>
>>> To add to what Robert advises -- there is no one-size-fits-all
>>> guidance that covers all situations. You have to understand the
>>> principles of operation and reason/measure from there. There are
>>> heuristics, but even then exceptions to the rules of thumb abound.
>>>
>>> As Robert said, in general the buffered channel will give you
>>> more opportunity for parallelism, and might move your bottleneck
>>> forward or back in the processing pipeline.
>>>
>>> You could try to study the location of your bottleneck, and tracing
>>> ( https://go.dev/blog/execution-traces-2024 ) might help
>>> there (but I've not used it myself--I would just start with a
>>> basic CPU profile and see if there are hot spots).
>>>
>>> An old design heuristic in Go was to always start
>>> with unbuffered channels. Then add buffering to tune
>>> performance.
>>>
>>> However there are plenty of times when I
>>> allocate a channel with a buffer of size 1 so that I know
>>> my initial sender can queue an initial value without itself
>>> blocking.
>>>
>>> Sometimes, for flow-control, I never want to
>>> buffer a channel--in particular when going network <-> channel,
>>> because I want the local back-pressure to propagate
>>> through TCP/QUIC to the result in back-pressure on the
>>> remote side, and if I buffer then in effect I'm asking for work I cannot
>>> yet handle.
>>>
>>> If I'm using a channel as a test event history, then I typically
>>> give it a massive buffer, and even then also wrap it in a function
>>> that will panic if the channel reaches cap() capacity; because
>>> I never really want my tests to be blocked on creating
>>> a test execution event-specific "trace" that I'm going to
>>> assert over in the test.  So in that case I always want big buffers.
>>>
>>> As above, exceptions to most heuristics are common.
>>>
>>> In your particular example, I suspect your colleague is right
>>> and you are not gaining anything from channel buffering--of course
>>> it is impossible to know for sure without the system in front
>>> of you to measure.
>>>
>>> Lastly, you likely already realize this, but the request+response
>>> wait pattern you cited typically needs both request and waiting
>>> for the response to be wrapped in selects with a "bail-out" or shutdown
>>> channel:
>>>
>>> jobTicket := makeJobTicketWithDoneChannel()
>>> select {
>>>   case sendRequestToDoJobChan <- jobTicket:
>>>   case <-bailoutOnShutDownChan: // or context.Done, etc
>>>       // exit/cleanup here
>>> }
>>> select {
>>>   case <-jobTicket.Done:
>>>   case <-bailoutOnShutDownChan:
>>>     // exit/cleanup here
>>> }
>>> in order to enable graceful stopping/shutdown of goroutines.
>>> On Monday, September 1, 2025 at 5:13:32 PM UTC+1 robert engels wrote:
>>>
>>>> There is not enough info to give a full recommendation but I suspect
>>>> you are misunderstanding how it works.
>>>>
>>>> The buffered channels allow the producers to continue while waiting for
>>>> the consumer to finish.
>>>>
>>>> If the producer can’t continue until the consumer runs and provides a
>>>> value via a callback or other channel, then yes the buffered channel might
>>>> not seem to provide any value - expect that in a highly concurrent
>>>> environment go routines are usually not in a pure ‘reading the channel’
>>>> mode - they are finishing up a previous request - so the buffering allows
>>>> some level of additional concurrency in the state.
>>>>
>>>> When requests are extremely short in duration this can matter a lot.
>>>>
>>>> Usually though, a better solution is to simply have N+1 consumers for N
>>>> producers and use a handoff channel (unbuffered) - but if the workload is
>>>> CPU bound you will expend extra resources context switching (ie. thrashing)
>>>> - because these Go routines will be timesliced.
>>>>
>>>> Better to cap the consumers and use a buffered channel.
>>>>
>>>>
>>>>
>>>> On Sep 1, 2025, at 08:37, Egor Ponomarev <egorvpo...@gmail.com> wrote:
>>>>
>>>> We’re using a typical producer-consumer pattern: goroutines send
>>>> messages to a channel, and a worker processes them. A colleague asked me
>>>> why we even bother with a buffered channel (say, size 1000) if we’re
>>>> waiting for the result anyway.
>>>>
>>>> I tried to explain it like this: there are two kinds of waiting.
>>>>
>>>>
>>>> “Bad” waiting – when a goroutine is blocked trying to send to a full
>>>> channel:
>>>> requestChan <- req // goroutine just hangs here, blocking the system
>>>>
>>>> “Good” waiting – when the send succeeds quickly, and you wait for the
>>>> result afterwards:
>>>> requestChan <- req // quickly enqueued
>>>> result := <-resultChan // wait for result without holding up others
>>>>
>>>> The point: a big buffer lets goroutines hand off tasks fast and free
>>>> themselves for new work. Under burst load, this is crucial — it lets the
>>>> system absorb spikes without slowing everything down.
>>>>
>>>> But here’s the twist: my colleague tested it with 2000 goroutines and
>>>> got roughly the same processing time. His argument: “waiting to enqueue or
>>>> dequeue seems to perform the same no matter how many goroutines are
>>>> waiting.”
>>>>
>>>> So my question is: does Go have any official docs that describe this
>>>> idea? *Effective Go* shows semaphores, but it doesn’t really spell out
>>>> this difference in blocking types.
>>>>
>>>> Am I misunderstanding something, or is this just one of those “implicit
>>>> Go concurrency truths” that everyone sort of knows but isn’t officially
>>>> documented?
>>>>
>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "golang-nuts" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to golang-nuts...@googlegroups.com.
>>>> To view this discussion visit
>>>> https://groups.google.com/d/msgid/golang-nuts/b4194b6b-51ea-42ff-af34-b7aa6093c15fn%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/golang-nuts/b4194b6b-51ea-42ff-af34-b7aa6093c15fn%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>>>
>>>>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
> To view this discussion visit
> https://groups.google.com/d/msgid/golang-nuts/8a063215-e980-4e93-a1b0-aac8bbd786b4n%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/8a063215-e980-4e93-a1b0-aac8bbd786b4n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
> To view this discussion visit
> https://groups.google.com/d/msgid/golang-nuts/818EF6F9-E919-477B-9B86-3DF8287EE9EC%40ix.netcom.com
> <https://groups.google.com/d/msgid/golang-nuts/818EF6F9-E919-477B-9B86-3DF8287EE9EC%40ix.netcom.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/golang-nuts/CANrC0BiJ-gOZbufDprFf51mwJbnQZEwPjY45_GfZHuCEkpNmCw%40mail.gmail.com.

Reply via email to