3 ms·
As someone whose work includes hardware driver programming, I'd rather not give up goto. It would make a lot of error handling extremely cumbersome, and much le
by arghwhat 9y ago
As someone whose work includes hardware driver programming, I'd rather not give up goto. It would make a lot of error handling extremely cumbersome, and much less readable. :(
However, I find "go" and "goto" to not intersect at all. I've written this many times in other comments on this thread, so I'd rather not type it out again, but the TL;DR: is that "goto" can make understanding a function when read difficult, while "go" is clear when read. No function is understood if never read, and spawning "background tasks" is a core part of asynchronous programming (and thus not an unusual side-effect).
The "nursery pattern" is a decent construct that I have used quite often whenever I felt a need, but it doesn't appear to really solve any issues mentioned in the post. I also elaborated on this quite a few times already, so TL;DR: the only real problem of goroutines is things like references to potentially closed objects, but any method may end up storing an internal reference used at a later call, making the issue not related to concurrency.
- stouset 9y ago> while “go” is clear when read… I believe our industry’s several decades of demonstrated inability to write concurrent code correctly disagrees with you. Languages like go (and Rust, to a greater degree) have improved the situation. But there are still a ton of sharp edges that existing approaches still have, and—often—they’re ones that aren’t readily apparent until after a project starts growing and begins unearthing issues that are rare, only happen on faster hardware, or require higher levels of concurrency than we’re previously used.
- arghwhat 9y ago> I believe our industry’s several decades of demonstrated inability to write concurrent code correctly disagrees with you. Not at all. Concurrent/parallel code isn't particularly difficult to write—there's just a unique class of problems related to it that might occur, but that does not mean it is hard. Rather, the industry has through several decades demonstrated an inability to write bug free code in general. I wouldn't blame concurrency/parallelism for this.
- camgunz 9y agoI guess we can debate what "hard" means, but I think "adds a lot more problems that are much harder to reason about and avoid" is exactly the definition of hard. If it's not, I'm honestly not sure what is. Torn reads, deadlocks, concurrent modification, the effects on optimizers, the overhead of scheduling and lock management. Each of these are deep, complicated issues you only have to deal with when writing concurrent (well, parallel) code. But yeah I mean, if you mean it's pretty easy to call pthread_create then OK. But if you at all care about your program working, then parallel code is much, much harder to write.
- arghwhat 9y agoAll the problems you present go into the category of sharing state. This is the unique problem presented by concurrent programming, which is no harder than all the other problems in programming. This does not mean that concurrent programming is hard, but that just like all other programming, it has some sharp edges. The issues related to sharing state can be easily avoided by, for example, using a CSP-style paradigm (channels in Go). In Go, this only leaves behind a deadlock, which are automatically panic, making it incredibly easy to debug. Overhead of scheduling and effects on optimizers are not related to concurrency/parallelism. The scheduler is affected by many things even under single-threaded execution which is far beyond the scope of normal application developers, and optimizers are largely unaffected by concurrent/parallel programming (although shared structures must internally present memory barriers). The optimizer is also beyond the scope of normal application development. If you are doing development that requires precise control over optimizations and scheduling (like I do), then all bets are off. However, with "normal" (i.e. not-processing-8x100Gb/s streams) programming, concurrent programming is a breeze unless you intentionally shoot yourself in the foot.
- camgunz 9y ago> ...which is no harder than all the other problems in programming. Well but for almost all of those problems you have something to help you. Yes manual memory management is hard, thanks GC/lifetimes. Yes error handling is hard, thanks exceptions (kind of). The point of OP is that practically all of the things we've built to help us with parallel/concurrent programming are still very low level, and your program still has to deal with the fallout in a way that it doesn't with GC or exceptions or other helpful abstractions. > The issues related to sharing state can be easily avoided by, for example, using a CSP-style paradigm (channels in Go). In Go, this only leaves behind a deadlock, which are automatically panic, making it incredibly easy to debug. There are only a few languages/platforms where CSP is feasible, and very few (none?) of them achieve the performance of CSP in Go because they don't/can't use segmented stacks. And even when using CSP you still need synchronization primitives. Parallel Go code uses mutexes everywhere. CSP in Go helps with _implementing_ parallel programs, but the Go team provides a lot of extra tooling to help _debug_ parallel programs that's well outside the definition of CSP --> the deadlock panic you cite is a good example actually. > Overhead of scheduling and effects on optimizers are not related to concurrency/parallelism. I disagree; mostly my evidence is "just google for volatile in C". ~~~ In general I still think parallel program is in a separate class of problems, but even if we stipulate it's just as hard as other problems, I still think we have much better tools for dealing with the other problems. I think we're getting there -- and CSP is part of that -- but it's definitely not the case that we've settled on a solution for 99% of problems.
- wilun 9y ago> I believe our industry’s several decades of demonstrated inability to write concurrent code correctly disagrees with you. But mostly so for the reasons nurseries try to solve (and actually manage to somehow solve some)? I'm not convinced. Lifetime of threads is usually easy compared to races/deadlock/etc. on the resources they use. Not to say we shall not use nurseries where applicable. But is it the new fundamental structure concurrency should be based on? Debatable. My wild guess is that the Rust approach of concurrency (even if it is not exactly on the same subject, but we are trying to find the fundamental way of structuring things) will have more impact.
- sterlind 9y agoInteresting; I program drivers but avoid goto entirely: NTSTATUS err = S_OK; if(!FAILED(err)) err = Op1(); if(!FAILED(err)) err = Op2(); ... if(FAILED(err)) /*cleanup*/
- arghwhat 9y agoThat would work, but I'm not sure I find it that pretty. Plus, it assumes all "ops" have compatible error output (which is unfortunately rarely the case). What I instead do is usually: int some_func() { int err = OK; if (some_op() != SOME_OK_CRITERIA) { err = SOME_OP_FAILED; goto error; } .... error: // do necessary cleanup return err; } I'm also mostly responsible for Linux and BSD kernel mode driver, as well as our user-mode driver (most of our driver exist as a shared user-mode component)—I wipe the Windows kernel mode driver off on someone else. :) I have no allergy to goto at all, as long as its not used in absurd fashions (like how it's used in a duffs device). If a goto seems easier/prettier, I use a goto.