The reason why Go's concurrency model may hit some limitations

Continuing the discussion from What is the theoretical / practical limit of golang vs c / c++? Statement of Rob on memory communication needs appending:

I’m curious about why Go’s concurrency model may hit limitations. What are the limitations? Are there any resources that I can refer to?


Limitations:

Resources:

The cited statement is vague. I am not sure what the author tries to say.

Maybe the author meant to refer to Go’s communication mechanism via channels. Channels are the manifestation of the Go proverb “Don’t communicate by sharing memory, share memory by communicating.”

Channels are easy to reason about, but sending data around as “messages” can be slower than manipulating data in-place. Go has mutexes and semaphores available if channels hit a performance limit or turn out to be unsuitable for complex data access situations. So there is a fallback if channels don’t scale well.

Unrelated to concurrency but worth keeping in mind: Go’s garbage collector can be a limiting factor on performance, although there are techniques available for limiting allocations (or even turn them to zero) in the critical path(s) of a program.

1 Like

One limitation is that Go doesn’t have the concept of NUMA. Tbh, I know very little about this, I’m not sure how relevant is this or whether this is relevant at all.

Another limitation is garbage collection. Your Go program only have one centralized GC. When you scan for live object, it locks the whole process. Go GC is generational GC, which means it is quite smart. Object that lives long enough won’t be scanned as often. So, if you can limit the number of short-lived object, you may reduce the GC overhead. But, in order to know which object is old or young, you need to do some kind of bookkeeping everytime you assign to a pointer, which can add overhead to your program as well.

Channel can be a limiting factor too. Golang’s channel is a builtin language feature, so it’s hard to experiment with that. For example, let’s say you have a better channel implementation than Golang. Maybe your implementation is better in performance, but not as generic (maybe it only allows a single consumer). Well, you can’t change golang’s runtime. You can build your own multi-producer-single-consumer channel in golang manually using sync.Mutex, and probably sync.Cond, but then you can’t make your own mutex.

Or maybe, you have your own scheduler that’s better than golang’s scheduler. Maybe it’s better for your usecase. Well, you can’t implement it in Golang. The scheduler is already part of Go. I guess, if you really want to try, you can clone Go’s source code, modify the runtime code, and compile it yourself, but it’s just easier to use another language like C++.

So, I think for language like C, C++, Odin, Zig, and Rust, there are more things you can control. In Go, some things are already implemented in Go runtime and it’s hard to change it.

1 Like