I'm glad that Go has matured to the point where this can be a historical footnote! Good work, Go team.
I'm glad that Go has matured to the point where this can be a historical footnote! Good work, Go team.
You can still benefit from the "claims of the language", even if code isn't running in parallel.
However, I think calling my response "technically true" is selling Go's concurrency primitives a little short. They enable a really great programming model, which makes it much easier to express certain ideas in code. And I/O-bound programs benefit even if GOMAXPROCS=1.
I didn't understand that subtlety when I first started with Go, and it seems like GP commenter (and probably other HN readers) didn't either. "Concurrency is not parallelism" is very helpful in explaining that, which is why I linked to it.
EDIT: Verification and validation [1] are another pair of terms where the distinction is important for industry, but popular understanding often mixes them up.
[0] http://en.wikipedia.org/wiki/Accuracy_and_precision
[1] http://en.wikipedia.org/wiki/Verification_and_validation
Interestingly there is one example of parallelism without concurrency and with just one processor and just one core - vector instructions. Although it would be a breathtakingly good compiler that could emit vector instructions to schedule two goroutines in parallel
For instance, the subtle difference arising from MIMD execution and SIMD execution. The former allows for concurrent execution, whereas the latter does not. However, both are parallel execution models. This isn't theoretical either as GPUs are strictly SIMD machines in their execution.
* Parallelism is a feature of the algorithm, chosen to make it run faster. E.g. you can use a parallel algorithm to factor a matrix. From a technical standpoint, the challenge of parallelism is how to deconstruct the problem so that each processing unit will be able to take on a share of the effort -- parallelism implies cooperation.
* Concurrency is a feature of the problem. E.g. many users using Facebook at once. If at any one time you have 10K requests, then your concurrency level is 10K, no matter how many cores are used. The challenge of concurrency is how to allocate computing resources among the competing requests -- concurrency implies competition.
Parallelism is orderly; concurrency is messy. Parallelism is a choice; concurrency isn't.
My reaction is somewhat like this XKCD: https://xkcd.com/169/
Great to see they have changed this to something that, without any other context, makes more sense.
You mean that it was good for writing network servers capable of handling many tens of thousands of connections? You usually don't need more than one core to handle IO, and if you are doing processor intensive stuff a few more cores is not going to help if you have many thousands of requests - in this case a better pattern is a work queue and a way of letting the client know their work is done.
Concurrency/threading is generally considered a "hard" topic within software dev, and if you lack a solid grounding in the basic concepts you are destined to misuse or misapply the paradigm.
Additionally, even rendering HTML templates can take quite a bit of CPU time and when Go's concurrency is limited to a single-core, that means only one template can be rendered in parallel, blocking other requests.
For what definition of "many"? If you have 16 cores that is 16 requests being processed in parallel. Go hasn't even got out of bed by the time it is handling 16 connections. When Go is being used for it's intended purpose - handling thousands of concurrent connections, you need to be a lot smarter than just running on all the cores you have.
I'm not saying you wouldn't use all cores to maximize utilization - what I am saying if that if you are scaling beyond one core, then you would already be thinking about scaling across multiple servers behind a load balancer - i.e. you are doing ops work on a production scale cluster. And if you are doing production ops work, you are going to be tuning all the processes under your control in detail, not just making rash assumptions about how they may or may not utilization multiple cores.
The point I was arguing here is that someone who said "Back when I was learning Go" and who understands the "nature of the claims of the language" is not going to be running into these sorts of scalability issues, and would not be bitterly disappointed to learn of a default threading value of 1. Unless they had some misconstrued understanding of what Go's concurrency support was all about, and made an incorrect assumption that it was primarily about parallel computation, rather than it's true purpose of having many thousands of lightweight processes handling lots of network I/O.
And latency is important. I can't have a user performing a lengthy operation, while at the same time blocking all other users. Real (not fake) threads are needed to combat latency.
And if you have thousands of current connections (the kind of workload Go was designed to handle), and only a few hundred of them are performing "lengthy operations", you could have 4 cores, or 16, it won't make much difference compared to 1 core - which is the vast majority of the connections are going to be blocked for unacceptably long intervals. In this scenario, the only solution is to either queue up work and make the client come back later, or scale across many servers behind a load balancer.
The point I am making here is that people who are engineering scaled out systems across multiple servers to handle thousands of concurrent requests are usually high-end ops people who are not going to be naively assuming that any process will be doing threading in a particular way - they would have already tested Go in details first and would very quickly have seen you need to set the GOMAXPROCS variable to use more than one core. They would probably look at the GC as well. Tuning stuff to run well is what ops people do, and choosing how many cores a process should use is pretty standard stuff (it may be sharing the server with other processes after all).
In my opinion, it is perfectly legitimate to have the requirement of low latency, while running lengthy operations in the background (I'd show a spinner or a progress indicator, a very common approach). Yes, at a certain point this will break down, at which point I will add more servers. This is better than having no such system at all.
If a language is only good for serving >>1M users and only doing I/O, then I guess it should not be branded as a general purpose, mainstream language, even if aimed at the web.
That's only part of my argument, and I don't think it is a strawman argument.
> In my opinion, it is perfectly legitimate to have the requirement of low latency, while running lengthy operations in the background
Ironically, this is a straw-man argument - I never said nor insinuated it wasn't a valid requirement. All I am saying is for the kind of scalability Go was designed for (1000s-100,000s connections), if you are doing lengthy operations, you will never be just thinking about multi-core, you will be scaling across servers.
> then I guess it should not be branded as a general purpose, mainstream language
Well I honestly am not sure it was. It was branded very early on as a language that is good for writing highly concurrent, highly scalable network servers. For example, integrating with C/C++ libraries is a pain, and slow. I would say most general purpose languages would have good C integration as a basic feature. And the lack of good C integration is a direct result of specializing Go at running millions of little Goroutines concurrently (segmented stack and all that).
I disagree, it makes total sense when you understand the consequences of GOMAXPROCS set to anything but 1.
Almost every person I know who started with Go got a long way in before doing some benchmarks and realizing that they were only using one thread. I know the scheduler had issues but I think setting the default to 2 would have avoided people going so far before learning that the language can't handle everything they need to care about.
“In computer science, concurrency is a property of systems in which several computations are executing simultaneously, and potentially interacting with each other. The computations may be executing on multiple cores in the same chip, preemptively time-shared threads on the same processor, or executed on physically separated processors.”
https://en.wikipedia.org/wiki/Concurrency_%28computer_scienc...
If you were to go back and reread my comment, note that I used the broader term concurrency at the start – which includes both classic async patterns within a single thread where operations may be interleaved but shared resources wouldn't be updated simultaneously as well as true parallel execution – and then specifically referred to parallel execution at the end as a source of potential pitfalls.
Data point: Inside Google we have been setting GOMAXPROCS=runtime.NumCPU for many years because most of the Go programs we run are highly concurrent network servers, for which a higher GOMAXPROCS value gives better performance.
As a consequence, answering the question of what other processes where on the same machine was not as easy as on an EC2 instance. We didn't mind though because Borg was so good at scheduling we pretty much didn't have to care about the machine level. Instead we thought at the datacenter level.