On Tue, Sep 2, 2025 at 2:40 PM Jason E. Aten <j.e.a...@gmail.com> wrote:

> David wrote:
> > The typical way is to include a channel of capacity 1 in the "message"
> that's going to the worker.
>
> I like this idea. But you have to guarantee only one observer will ever
> get to check once
> if the job is done.
>
Yep, usually the channel is only in the message sent to the worker as a
sending channel and the "whole" channel is left as a local variable (where
it was created).

>
> On Tuesday, September 2, 2025 at 7:37:26 PM UTC+1 David Finkel wrote:
>
>> On Tue, Sep 2, 2025 at 2:27 PM robert engels <ren...@ix.netcom.com>
>> wrote:
>>
>>> 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.
>>>
>> If they're all initiating a send at exactly the same time, yes, there's
>> no ordering. However, if there's any temporal separation, the ordering of
>> sends completing will be FIFO. (it's a linked-list-based queue)
>>
>>>
>>> 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.
>>>
>> I disagree, that response has to be sent somehow.
>> The typical way is to include a channel of capacity 1 in the "message"
>> that's going to the worker.
>>
>>>
>>> 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.
>>>
>> Right, that's another reason why it's a bad idea to have the response
>> channel unbuffered. (beyond unbuffered writes introducing goroutine
>> scheduling dependencies).
>>
>>>
>>> On Sep 2, 2025, at 13:07, David Finkel <david....@gmail.com> wrote:
>>>
>>>
>>> On Tue, Sep 2, 2025 at 11:59 AM robert engels <ren...@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....@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...@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...@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...@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/f91e450d-a660-41d9-8ed0-58af18d2f473n%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/f91e450d-a660-41d9-8ed0-58af18d2f473n%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/CANrC0Bh2757E_Av1RHfF-C%3DFVy8%2BEWhq2QOa1A_S0%3D%3Dde0Wtgw%40mail.gmail.com.

Reply via email to