4 ms·
Abstractions should really make it easier than what's described in the article. There's no reason that in 2024, after almost fifty+ years of computer science, w
by yyyfb 3y ago
Abstractions should really make it easier than what's described in the article. There's no reason that in 2024, after almost fifty+ years of computer science, we still have articles saying "hey, here's a common pitfall that will throw no errors or warnings at you, but will deadlock non-deterministically one time out of one billion".
- PaulDavisThe1st 3y agoSometimes, this is precisely what you need. The problem with what is described in the article is the race condition. If you hit the race on your one pass through this code, you're dead. But there are other conditions whether you know you will repeatedly pass through the code, and the race doesn't matter. So the language/programming model has abstractions that can be used successfully for the latter. This has the unfortunate side effect of them also being capable of being used to create bugs in the former. If you want a real world example of this sort of thing, consider a single-reader/single-write circular buffer/queue. You don't need any sort of interlock between the read ptr/index and the write ptr/index for this sort of data structure because if they mis-ordered (ie. the writer has added more data but not yet updated the write ptr), the worst that can happen is that the reader thinks there is less data in the buffer to read than there actually is. However, that analysis relies on the model that the reader and writer are periodically checking the data structure. If instead they are both doing a single write and a single read, this same design will lead to loss of data flow between them. To ensure this doesn't happen, you need a mutex that they both share. So is the right thing to do to require any such shared data to be protected by a mutex? Well, if you're only goal is to prevent data races, then sure. But if you also care about e.g. realtime behavior, then absolutely not. This is just one example. There are many more cases where in case A you don't want or need synchronization primitives and in case B you absolutely do.
- lostmsu 3y agoI would agree with GP comment: in the particular case described in the article using channels instead of lower-level primitives would solve the problem. E.g. when workers need to be stopped you close the channel sender, and receiver returns appropriate result to all wait calls.
- PaulDavisThe1st 3y agoThat's a highly specific solution that doesn't address the fundamental interplay between atomics and various kinds of synchronization primitives.
- lostmsu 3y agoThis objection of yours was directly addressed by the comment I'm trying to support. E.g. yes, it is important to know the low-level details, but also the abstraction chosen is just not fit for the purpose. And it's partially fault of C++ because channels are not in the standard library.
- yyyfb 3y agoI'm familiar with the trade-off. But I disagree that there's a choice to make between safe and powerful. It is possible to make safe behaviors by default, while leaving the door open for more advanced behaviors. I guess code linters can help as crutches. But the right abstractions too. It's the motivating example for languages like C++ in the first place (over assembly): type safety, easier reasoning about control flow, etc. Note how the case here is very simple - waiting on a variable to change - but advice just boils down to "be more careful" instead of "here's the foolproof way to do this". You can only ask a programmer to watch out for so many things, and C++ has way too many of these things.
- PaulDavisThe1st 3y agoIt's not safe vs powerful, typically. It's more often safe versus performant.