Earlier quoted context omitted.
Rust async streams or rayon come very close to what you describe as structured concurrency. Actually much closer than anything I saw in other mainstream languages eg Java or Go.
Rayon is about as pure an example of it as you can imagine. In a lot of cases you just need to replace iter() with par_iter() and it just works.
Go’s race detector has a mutex blind spot
51–60 of 60 posts
Re: Go’s race detector has a mutex blind spot
#52Earlier quoted context omitted.
> and 'f' panics, the program's deadlocked forever I don't see `mu.Lock(); f(); mu.Unlock()` anywhere really. `mu.Lock(); defer mu.Unlock(); f();` is how everyone does it to prevent that possibility.
From the golang github org in non-toy code: 1. https://github.com/golang/tools/blob/f7d99c1a286d6ec8bd4516a... 2. https://github.com/golang/sync/blob/7fad2c9213e0821bd78435a9... There are dozens and dozens throughout the stdlib and other popular go code. The singleflight case is quite common, if you have: mu.Lock() if something() { mu.Unlock() moreWorkThatDoesntWantMutex() return } mu.Unlock() most gophers use manual…
Re: Go’s race detector has a mutex blind spot
#53Re: Go’s race detector has a mutex blind spot
#54https://go.dev/blog/race-detector
> Because of its design, the race detector can detect race conditions only when they are actually triggered by running code, which means it’s important to run race-enabled binaries under realistic workloads.
This isn't a mutex blind spot. It is a side effect of goroutine/thread scheduling, which will obviously be based on workload and other factors. There are a bunch of other cases, that are not mutex related, that it won't see unless execution actually triggers them.
Re: Go’s race detector has a mutex blind spot
#55Earlier quoted context omitted.
As a sibling said, Go has all the same deadlocks, livelocks, etc you point out that rust doesn't cover, in addition to also having data-races that rust would prevent. But, also, Go has way worse semantics around various things, like mutexes, making it much more likely deadlocks happen. Like in go, you see all sorts of "mu.Lock(); f(); mu.Unlock()" type code, where if it's called inside an `http.Handler` and 'f' panic…
> and 'f' panics, the program's deadlocked forever I don't see `mu.Lock(); f(); mu.Unlock()` anywhere really. `mu.Lock(); defer mu.Unlock(); f();` is how everyone does it to prevent that possibility.
I personally haven’t worked with any of these perfect programmers, but I’m glad that you do.
Re: Go’s race detector has a mutex blind spot
#56Earlier quoted context omitted.
From the golang github org in non-toy code: 1. https://github.com/golang/tools/blob/f7d99c1a286d6ec8bd4516a... 2. https://github.com/golang/sync/blob/7fad2c9213e0821bd78435a9... There are dozens and dozens throughout the stdlib and other popular go code. The singleflight case is quite common, if you have: mu.Lock() if something() { mu.Unlock() moreWorkThatDoesntWantMutex() return } mu.Unlock() most gophers use manual…
Maybe it's stockholm syndrome, but the "manual" unlocks in bite sized funcs don't bother me -- it's easy to see that it's going to be unlocked by the time the function returns.
At a minimum now you’ve also got to scan for nil dereferences. Maybe that simple code between the mutex is pulled out into a function in a later commit. Then the function body is changed in the next commit to do something less simple.
This is all depressingly reminiscent of listening to die-hard C enthusiasts insist that avoiding use-after-free bugs and out of bounds reads and other issues are easy as long as you write code perfectly all the time. It’s easy as long as you just always do the right thing.
It’s okay to admit your language of choice has weaknesses. You don’t have to stop using go just because you can acknowledge that data races can creep in if you’re not diligent enough.
Re: Go’s race detector has a mutex blind spot
#57Earlier quoted context omitted.
I would say it is a Go specific problem with how mutexes and defer are used together. In rust you would just throw a block around the mutex access changing the scoping and ensuring it is dropped before the slow function is called. Call it a minimally intrusive manual unlock.
In Rust you can also explicitly drop the guard. drop(foo); // Now foo doesn't exist, it was dropped, thus unlocking anything which was kept locked while foo exists If you feel that the name drop isn't helpful you can write your own function which consumes the guard, it needn't actually "do" anything with it - the whole point is that we moved the guard into this function, so, if the function doesn't return it or store…
Re: Go’s race detector has a mutex blind spot
#58You're using Go's race detector wrong if you expect it to actually catch all races. It doesn't, it can't, it's a best effort thing. The right way to use the go race detector is: 1. Only turn it on in testing. It's too slow to run in prod to be worth it, so only in testing. If your testing does not cover a use-case, tough luck, you won't catch the race until it breaks prod. 2. Have a nightly job that runs unit and int…
The data race patterns in Go article from Uber is always a scary read. https://www.uber.com/blog/data-race-patterns-in-go/
I’m not sure how else you can explain perfectly idiomatic code (a loop, or a reused err variable, or a closure) causing a program to fall on its face simply by dropping in go’s namesake feature, whose entire purpose was supposed to be that you could simply drop it in.
To actually use `go` you have to do minor contortions like always remembering to copy your loop variables, make new error variables, and also not accidentally capture any external variables in a closure.
Go doesn’t actually help you with any of this, of course. You just have to remember to do it right every single time. None of these things are hard (usually), but the fact that you have to do them at all speaks volumes about the amount of forethought that went into it.
And of course doing all of those steps doesn’t save you if one of the things you tried to copy secretly contains a pointer inside of it, like absolutely everything in golang does. You didn’t know, and it wasn’t even a public member so it wasn’t in the docs. But there was a pointer somewhere deep inside the thing you copied so now you’ve got unguarded concurrent mutation of shared memory.
Re: Go’s race detector has a mutex blind spot
#59Earlier quoted context omitted.
OK, but you're not in "Go"-specific problems any more, that's just concurrency issues. There isn't any approach to concurrency that will rigorously prevent programmers from writing code that doesn't progress sufficiently, not even going to the extremes of Erlang or Haskell. Even when there are no locks qua locks to be seen in the system at all I've written code that starved the system for resources by doing things li…
> OK, but you're not in "Go"-specific problems any more, that's just concurrency issues. It’s absolutely a go-specific problem from defer being function scoped. Which could be ignored if Unlock was idempotent but it’s not.
This alleviates all these problems of unlocks within if bodies at the cost of an indent (and maybe slight performance penalty).
Re: Go’s race detector has a mutex blind spot
#60Earlier quoted context omitted.
Maybe it's stockholm syndrome, but the "manual" unlocks in bite sized funcs don't bother me -- it's easy to see that it's going to be unlocked by the time the function returns.
Unless there’s a bug and it panics? At a minimum now you’ve also got to scan for nil dereferences. Maybe that simple code between the mutex is pulled out into a function in a later commit. Then the function body is changed in the next commit to do something less simple. This is all depressingly reminiscent of listening to die-hard C enthusiasts insist that avoiding use-after-free bugs and out of bounds reads and othe…
The examples pointed to in the stdlib to complain about that usage pattern could have easily done just that -- but they were optimized to shrink the window of the locking time. My take is that the code paths were simple enough to verify that it was safe to do, and being in the stdlib had both. enough eyes to validate and a driver (being foundational code).
I recognize that Go is far from perfect but think some of the pearl clutching is manufactured (OMG! nil pointers! -- they technically can happen but in my many years of coding in Go it hasn't been a problem of note.)
And of course we should recognize that the locking issue being brought up was a simple one and the story gets worse when talking about complicated concurrency. Then the Go story of "just use the race detector!" is tempered by the fact that it is only best effort and no guarantees that it's found everything (like a bloom filter of certainty).
So year, Go has footguns and other inadequacies (FFI is a bummer). But it's good enough for a lot of things. Rust is on my list to learn, so my only complaint about it is the steep learning curve and I'm an old lazy dev.