3 ms·
Indeed. I like the push approach outlined here: http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/ http://marcio.io/2015/07/handling
by kitd 6y ago
Indeed.
I like the push approach outlined here:
http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/ http://marcio.io/2015/07/handling-1-million-requests-per-min...
I have done a more generic version of this with success.
You have N workers, each reading from a separate input channel for work to do.
Then you have an outer "channel of channels", holding those worker channels. When you want to submit work, select a worker channel from the outer channel, submit, and push the worker channel back on the outer channel when done.
- knorker 6y agoCore people in Go have enlightened me that the "thread worker" model in Go is non-idiomatic. It breaks contexts (and all that implies, like credentials, tracing, timeouts) and stacks (making it harder to debug). You should just start the goroutine instead of enqueueing it. More info here: https://news.ycombinator.com/item?id=25831844 https://news.ycombinator.com/item?id=25831844
- kitd 6y agoI agree, it may break contexts, but the "idiomatic" form is mentioned in the article I linked and was found much less scalable than the final version. It's all tradeoffs, I guess.
- rakoo 6y agoI guess it's because on one hand the context is handled by the function directly (with the usual `process(ctx, job)` pattern) and on the other hand the context needs to be part of the job, which is less idiomatic ? Functions vs Types. Interesting. The issue with spawning goroutines all the time is that spawning, while cheap individually, can become expensive if done in large numbers (like 1 Million times a minute as in the article). Is that not considered problematic ?
- knorker 6y agoYeah, in the longer comment I said that it "complicates" contexts, because you can put the context in the work unit. But it's very easy to get it wrong. E.g. the godoc for the context package itself says "Do not store Contexts inside a struct type". I understand the reason to be that it's easy to make the lifetime nonobvious, and make it "strange" (hard to read and reason about) if it's not very strictly kept under control. In other words it becomes a foot-gun as the code base grows. And even then the stack is still unhelpful. Benchmarking for your own workload beats anything else no matter what the language, of course. But it may change in the future, too. Maybe the next version of Go actually makes it faster? In other words it's not wrong, but great care should be used. One reason it can be faster to spawn goroutines is that IIRC spawning a goroutine actually makes the current OS thread start running that. And if it completes then it can jump back to the spawner. In other words spawning goroutine can save thread creations and context switches, while a goroutine pool will likely incur an OS context switch, with cache implications and other complex interactions. But yes, measure with your actual workload is king.
- sagichmal 6y agoIt's not necessarily idiomatic or nonidiomatic, it's just a pattern that's appropriate in some circumstances and not appropriate in others. If you want best-effort asynchronous job processing semantics, then it's totally appropriate to use something like what's described in the article -- ideally with a lot less code :) If you want request-response, then, yeah, this isn't appropriate, and I agree that you should do whatever work in the request goroutine, blocking as necessary.