What makes it better than Mutexes?
评论:
mwholt:
shomali11:Who said it's "the correct approach to concurrency"? If by message passing, you mean channels, they serve different purposes than mutexes. Use channels for communicating and synchronizing the work of goroutines; use mutexes to share memory safely.
iwsbfgl:Well said
rageofsparta:Others are talking about the differences, but I think they are missing the why.
TL;DR: It isn't that message passing the the correct approach, it is that it is a very common need in concurrency. Mutexes are lower level.
Message passing is the most common need for safety in concurrency and in most popular languages, that safety has to be managed manually. But occasionally, there are times when moving data safely is not what you need in concurrency and mutexes are great for that (or channels and goroutines are too slow for you bottleneck).
I have a lot of experience with Java and quite a bit of the dealt with multithreaded tasks, as many complicated problems are. Most of the time I would use patterns in Java to do one main thing, send data to a thread or out of a thread. If it wasn't a performance bottleneck, I would use a synchronization block (or on the method itself), or if it was a bottleneck, work with locks (like mutexes). I have found that a lot of this work is a producer/consumer work, and it was easy to screw up the passing of data from the produce to the consumer.
So, with Go, you have channels (message passing). It makes it easy and performant enough to safely pass data from a producer (say a listener on a socket like HTTP) to a consumer (a request handler). Or say you want to scan a filesystem and backup data, have a scanner add files to a message queue and the consumer can just block waiting for file.
Without channels, you would need to create data structure, many times a slice or an array, give pointer to it in multiple threads, add mutexes around every access of that data structure. And if someone else later comes and adds some new method which deletes something, but forgets to add the mutex, or god forbid, forgets to release the mutex, you have a ticking timebomb that is hard to debug because that only happens in prod under some unknown circumstances. It is very easy to just let Go handle those messy details. It isn't going to forget to release or grab a mutex.
Yes, I could do all that manually, but I don't want to. Just like I don't want to manually manage memory. Yes, I could get better performance and a smaller footprint to manage my own memory, but I just don't want to. Other people that are much smarter than me and with way more experience have found a way to give me the chance to not worry about mutexes for common cases.
Oh, also, many other concurrency problems are just message passing in disguise. A thread had an error and you want to send the to another thread, like the UI or controlling thread? Sure sounds like a message to me. Why bother with an error var that needs to get set/read with mutexes?
Using channels isn't less error prone either. Particularly for beginners and those unfamiliar with message passing paradigms.
You could:
- have a blocking channel and leak the goroutine
- send on a closed or nil channel
- close the channel non-idiomatically
- do racy channel receive loops
The API surface for channels is greater than that of mutexes. So sometimes it's simpler to use a mutex and accurately get stuff done.
A thread had an error and you want to send the to another thread, like the UI or controlling thread? Sure sounds like a message to me. Why bother with an error var that needs to get set/read with mutexes?
However, this I agree +100.
iwsbfgl:dlsniper:I completely agree with you. I really just rambled my way through the fact that channels and goroutines are meant to help solve a very common problem. They are not perfect, but I like to think that they are a step forward.
robe_and_wizard_hat:See: https://blog.golang.org/share-memory-by-communicating, https://www.youtube.com/watch?v=PAAkCSZUG1c&t=2m48s and https://www.youtube.com/watch?v=PAAkCSZUG1c&t=4m20s for some more information on this.
gopherman12:Use channels when you need to pass information from one goroutine to another, and if you just need to hide access, mutexes or rwlocks will work just fine
gbitten:It eliminates the occurrence of race conditions by design. For example, if another goroutine needs to access a data at a memory location, it can't just read it but instead need to wait until the data is passed to it through channel.
emilepels:Besides what was said by others, the channel's abstraction is a lot easier to apply without logical flaws (like deadlocks) than mutex and semaphore.
chmikes:Do not communicate by sharing memory; instead, share memory by communicating.
flatMapds:Looking at the answers, I see a missing justification that makes channels more useful than mutexes.
The select instruction allows you to wait on multiple channel events. So if your goroutine has to wait on some input channel, it is handy to use another channel for signaling some event by just closing it for instance. The most typical use case is to signal the 'done' event when you want the goroutine to terminate.
So a channel may also be used for signaling with a select instruction.
Well it depends on your usecase, ie for things where in java you have a task that is geared towards shared memory, go for it, or use an atomic reference, if you are doing things that is just straight computation, or io, I would use Goroutines.
In some scenarios like if you want to make a process group, that shares state, I would use both locks or atoms / and channels.
Using straight message passing in a stateful service, is stupid and in my experience with systems programming, I often write stateful services.
There is no one size fits all approach to concurrency, message passing is just a useful pattern. Just using locks isn't exactly smart either, it will really slow your shit down, and with over use is prone to deadlock.
In all honesty however, I avoid using Go, for things that heavily deal with shared state, or needing a lot of logic in the control plane, and find it's sweet spot to be an area where you are writing something bruteforcey that requires high throughput.