Yes, but without external synchronization you have no ordering on the senders - 
so which actually blocks waiting for the receiver is random, further writers 
are blocked and are added to the list, but there is no ordering within them.

As the OP described, the writer needs to wait for a response, so the “goroutine 
doesn't have to wait for another goroutine to schedule in order to pick up the 
next work in the queue.” doesn’t apply.

So having unbuffered on both the request and response channels when you only 
have a single worker simplifies things - but if the requestor doesn’t read the 
response (or none is provided) you will have a deadlock.

> On Sep 2, 2025, at 13:07, David Finkel <david.fin...@gmail.com> wrote:
> 
> 
> On Tue, Sep 2, 2025 at 11:59 AM robert engels <reng...@ix.netcom.com 
> <mailto: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 
>>> <mailto: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 
>>> <mailto: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 
>> <mailto: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 
> <mailto:golang-nuts+unsubscr...@googlegroups.com>.
> To view this discussion visit 
> https://groups.google.com/d/msgid/golang-nuts/CANrC0BiJ-gOZbufDprFf51mwJbnQZEwPjY45_GfZHuCEkpNmCw%40mail.gmail.com
>  
> <https://groups.google.com/d/msgid/golang-nuts/CANrC0BiJ-gOZbufDprFf51mwJbnQZEwPjY45_GfZHuCEkpNmCw%40mail.gmail.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/13819247-4FDD-4AD9-9096-D30F51793CC5%40ix.netcom.com.

Reply via email to