On Tue, Sep 2, 2025 at 2:27 PM robert engels <reng...@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.fin...@gmail.com> wrote: > > > 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 > <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/CANrC0Bg66Pi5fDxKeXe9Dp9LOCbJpKtAT2HdeyXy1Wp%2BrOegBw%40mail.gmail.com.