11 ms·
Must be this tall to write multi-threaded code (2015)
- quantified 4y ago2015
- gumby 4y agoNot that anything has improved since then.
- xwdv 4y agoAye, it feels like we’ve mostly reached a plateau and arrived here through short terminism.
- hedora 4y agoWell, Rust is replacing C++ incredibly quickly.
- Kab1r 4y agoGo, Erlang/Elixir maybe, but as much as I like Rust, it has not made writing _concurrent_ programs easier imo.
- hedora 4y agoI write concurrent systems code for a living. Rust is much easier to get right than go, because it uses the borrow checker to statically check for correct synchronization. (Check out tokio.) I haven't used erlang/elixir.
- spacechild1 4y agoCitation needed.
- hedora 4y agoAmazon somewhat quietly replaced the S3 service implementation with a rust reimplementation, then announced it after the transition landed without making any bad headlines. I've heard Microsoft has moved to Rust for all new projects (there is an exception process for C++, of course). Parts of Chrome use it now. Support for using Rust in the mainline Linux kernel should land soon.
- quantified 4y agoI can't see "replacing" but at my employer and anywhere else that care to tell me personally about their stack, new stuff is in Rust.
- jerf 4y agoI think it's gotten easier over time, actually, and even when this was written it was theoretically out of date. There is a famous paper about how bad threads are from the 1990s. I believe it is valid on its own terms but misdiagnoses the underlying issue. The underlying issue is this: Taking multiple locks at once is not a sane operation at scale. Any system built on this will fail. There is no magic bullet that solves all concurrency problems, but a combination of techniques, used with discipline, take it from insanely impossible to merely challenging: 1. Assign data to a thread, with only that thread able to access it. Offer concurrency via message passing to that thread alone, and don't allow references to escape from the thread (pointers, whatever you want to call it). Language support for this is nice (Erlang and Pony do it one way, Haskell another, Rust yet a third) but not technically necessary. 2. Using locks is valid, and even quite helpful and useful, but never let yourself take more than one. If you need transactions, see #1. Don't write code that encourages people to take more than one. Don't forget about things that may take locks without you thinking about it (e.g., in Go, taking a mutex and then trying to send on a channel is taking two locks); this is one thing that can be a bit of a challenge. 3. For more complicated things, use libraries as much as possible that have solved the super complicated cases already. For instance, databases that offer transactional security. You can do this even if you don't otherwise "need" such databases. Or libraries that offer a "parallel map" within your language, or other canned solutions to parallelism/concurrency. 4. Use whatever support your language provides for "linting" your conditions. The Go race condition checker is theoretically insufficient but practically useful (passing it doesn't mean that you have a safe program but it's a good start). Your language's mileage may vary but take advantage of what you can. Honestly, just these things taken together can take you quite far. Again, these aren't magical solutions. There is an irreducible sense in which threading is harder than not threading, and I don't expect that to ever change. There will always be programmers that are more expert in it than others. But it doesn't have to be an impossible challenge anymore. And to a large degree, the challenge becomes less "solving the multithread problem" and more just figuring out how to architect systems under the restrictions of the patterns that work and compose together. Although all that said, at a browser scale concurrency is always going to be a problem. They gotta go fast, but that fights the safe patterns pretty hard because they do involve a certain amount of runtime slowdown (e.g., passing a message is always going to be slower than just not passing a message). Rust is the only practical solution at that scale. But just as You Are Not Google, You Are Not Writing A Browser. More normal programs can go a long way with just what I laid out above.
- macintux 4y ago> In this approach, threads own their data, and communicate with message-passing. This is easier said than done, because the language constructs, primitives, and design patterns for building system software this way are still in their infancy. “Infancy”? Erlang was invented in the 80s.
- moonchild 4y agoAnd the CSP paper dates to 1978.
- tonnydourado 4y agoOP does say system software, and although Erlang can have amazing uptime, I'm not so sure about it's raw performance story (on the other hand, I do remember seeing some insane benchmarks from a elixir framework, so, maybe I'm wrong). Also, other than Erlang and Elixir, which other reasonably mainstream language has this as first class features? Even Rust doesn't really put queues and message passing front and center, it just makes it easier to get away with doing traditional thread programming. Things can be old in years since discovery/invention, but still in it's infancy in usability and adoption.
- kubanczyk 4y agoGo has channels (`chan`) which it considers more native than e.g. Mutex. The latter is in a stdlib, the former is a feature of the language proper. Alas, the Go ecosystem could use more channels. I'd say that the adoption is still at the infancy stage. I wonder whether there are any practical shortcomings or is it just a readability tradeoff (the Mutex tends to be super-readable, where the channels not so).
- Lammy 4y ago> Also, other than Erlang and Elixir, which other reasonably mainstream language has this as first class features? Ruby 3.0+ with Ractors: https://docs.ruby-lang.org/en/master/ractor_md.html#label-Communication+between+Ractors https://docs.ruby-lang.org/en/master/ractor_md.html#label-Co...
- 4y ago
- bena 4y agoI think warnings like this are good and necessary. A lot of people are going to ignore this warning simply because they wrongly believe they're good enough to ignore it. And they're going to try and write this massive project simply to prove that the warning doesn't apply to them. And they will serve as great examples of why that warning is there in the first place. But there's another group. This group heeds the warning. They do their best to not violate the warning. But then they run into an issue. They exhaust every possible option trying to resolve the issue. But eventually, they realize that if they can violate the warning, just a little bit. And they're careful. And they violate the warning in the least way possible and take every action deliberately. And it works. Because the warning doesn't mean it's impossible, it just means that it is incredibly difficult and should be approached with the utmost care. Eventually, we do learn the pitfalls, internalize them, abstract them away, etc. And then we can even do away with the warning. Because it's no longer the edge of the universe, it's just another stop along the way.
- chasil 4y ago"If the fastest chips have N cores, a mostly-single-threaded program can only harness (1/N)th of the available resources. As N grows (and it is growing), parallel programs will win." This win will be constrained by Amdahl's law. A single program/process that is multithreaded can effectively use only a finite number of cores. To use more resources, the task must be broken into multiple processes. It can be faster to run $(nproc) number of gzip processes in parallel than to use pigz, for example. https://en.wikipedia.org/wiki/Amdahl's_law https://en.wikipedia.org/wiki/Amdahl's_law
- cortesoft 4y agoAlso, the major change with having so many cores is the ability to do lots of different things at once on a single machine. You don’t need any single process to utilize all the cores on a machine, you take advantage of all those cores by having a single machine do a lot do things.
- ajuc 4y agoDoesn't help you at all if you want the game to run at 60 fps and instead you can run it 8 times in parallel at 30 fps each.
- chasil 4y agoIt remains important not to buy cores that you cannot use, especially if there are other constrained components that would have a larger performance impact. Sophie Wilson (creator of the ARM instruction set) spends some time on these issues, also stressing core throttling and shutdown due to heat. https://www.youtube.com/watch?v=zX4ZNfvw1cw https://www.youtube.com/watch?v=zX4ZNfvw1cw
- spacechild1 4y ago> This win will be constrained by Amdahl's law. A single program/process that is multithreaded can effectively use only a finite number of cores. This assumes that the workload is fixed. A classical example would be compiling source code: you have a fixed number of source files and the compiler is only able to parallelize so much. On the other hand, there are applications where more threads allow you to increase the workload itself. For example, a modern DAW (digital audio workstation) is able to render tracks in parallel; if you have more threads, you can use more tracks with more plugins. In a computer game, more threads might mean more complex simulations, more objects, better graphics, etc.
- TheMagicHorsey 4y agoErlang made it pretty easy. But then not everyone wants to learn Erlang. And if you start writing NIFs all crazy, you might get yourself into trouble again. But it seems tractable. Am I being naive?
- hereforphone 4y agoNo, you're absolutely right: Erlang is magnificent and superior.
- ntoskrnl 4y agoSeems to be a theme at Mozilla. > Must be this tall to use `unsafe` https://i.stack.imgur.com/w9XMh.jpg https://i.stack.imgur.com/w9XMh.jpg
- khuey 4y agoThat's more or less the same spot in Mozilla's SF office.
- ChrisMarshallNY 4y agoLow-level thread programming is a minefield. I've been doing IRQ/concurrent programming since the 1980s, and still hate it. Thread bugs are a nightmare. Concurrency needs to be "baked into" higher-level languages, development APIs, and standard libraries. Basically, covered in bubble-wrap. That's starting to happen. It won't be as efficient as low-level coding, but it is likely to still have a significant impact on the performance of most code. And folks that are good at low-level threading would be well-served to work to support this.
- nick_ 4y agoPony has concurrency baked in via high-perf actor implementation. It's really nice. I believe Go has this as well, but it also has low-level concurrency API as well?
- skohan 4y agoIdk I think it is not as difficult as it is sometimes framed. I think the key is to design your program/separation of concerns in such a way as to make it easy to reason about concurrency, and minimize the need for synchronization points. I think a lot of the problems arise when you just try to write normal synchronous code, or to parallelize code which was originally synchronous, and don't realize the implicit constraints you had been relying on which no longer hold when concurrency is introduced.
- ChrisMarshallNY 4y agoFP is good for adapting to threading, but it has difficulties, when applied to things like GUI programming, async communications, or device control (places that need threading). A lot of languages are getting things like async/await/actor, etc., but even that is still too low-level for a lot of programmers. It can easily turn into a lot of single-threaded code. It needs to be completely under the surface. Swift does that with generics (I suspect other languages do it, as well). For example, you can say Array<Int>, or [Int]. They mean the same thing, but one does not have the generic syntax. If we can do similar stuff with things like mutexes and syncing, then it will go a long way towards highly performant, safe, code.
- bitwize 4y agoI knew I was right to trust Steve Summit with multithreaded stuff back when I worked with him!
- JoeAltmaier 4y agoThere was a paper long ago that showed duality between semaphore/locking code and message-queuing code. So folks figured they were the same. Not so! semaphore/locking is very hard if more than one semaphore exists. Look at the 'dining philosophers problem' and so on. But queuing! Done right, that can be proved correct through static analysis. Do threads that consume one queue, block on another queue? Draw the graph of queue-blocking - does it loop anywhere? Then you could have a problem. I.e. if your message-consumers don't block at all, then you cannot have a problem with deadlock. You CAN have a problem with messages stalling however - languishing on a list waiting for something to complete that might never complete. But at runtime this can be debugged fairly easily - if all your queues are visible to the debugger. In the semaphore implementation, the locking situation depends on the state of threads and their stacks, which all have to be frisked and back-executed to find out who holds what lock etc. Not always debugger-friendly to do. I favor a multi-threading environment of threads dedicated to queues, and all the queues visible to the debugger. That kind of setup has never done me dirty.
- Etheryte 4y agoThis sounds interesting and I've implemented simple ideas similar to the pattern you describe before, however I haven't read about its use in depth. Do you happen know of an article/book/resource that describes this along with real world experiences? If not, would you mind writing a blog post or article on it please?
- JoeAltmaier 4y agoI had the benefit of cutting my teeth at my first job, working on a message-passing OS. CTOS, based on RMX/86 used messages for everything. It was a very early networked computing system. Diskless workstations where file messages simply got forwarded to a server etc. And all the messages and queues were visible in the debugger! So I learned good thread hygiene right out of school.
- anonymousDan 4y agoOne trick I used to use when writing multithreaded java code for debugging purposes was to never use the built in synchronized/implicit object lock, since that made it quite difficult to understand from a stack trace what object was being locked. Instead, we would define an explicit inner lock and lock on that explicitly. The class name would then show up in stack traces making it much easier to track down.
- eftychis 4y agoThe problem with concurrency and parallelism is ownership — mainly that is write access than coherence. I have seen again and again the bad habit of people to bypass well defined set ups of data ownership because they feel they are smarter, it is unnecessary, we will figure it out down the road, and my favourite: it doesn’t solve deadlocks so why use anything as some bug families remain. The issue is not of frameworks or type systems but of companies and people. Engineers know this stuff by training, but either are new and commit hubris or management makes them (or maybe some of them are simply untrained — and somehow never went to a bank (queues)). P.S. For fun instance, I have seen unsafe used to bypass Rust’s type and ownership system to keep track of db snapshots in a “nice setup.” Rust was not wrong.
- tialaramex 4y ago> it’s critical to explore ways to incrementally add safe concurrency in C++. I don't buy it. Some things are foundational and attempts to add them incrementally are doomed because your foundations won't support the work. After building them easy things seem easy - but then hard things tear out your inadequately founded "incremental addition" and make a huge mess everywhere. Google has a team doing this sort of thing (indeed David Baron might even be on it for all I know), for exactly the same reason, they wrote a web browser in C++ and it has threading problems and they wish they could fix that. So Mozilla isn't exploring terra incognita here, and if there's something to be found I hope they find it, but I expect nothing.
- nemothekid 4y ago>So Mozilla isn't exploring terra incognita here, and if there's something to be found I hope they find it, but I expect nothing. This post was written in 2015. Looking back I think we can infer: 1. They found a solution (create a new language) 2. From the success of that language, it seems to deliver on its promise (although it has its warts) 3. They failed to incorporate that into the web browser because rewriting a web browser as a momentous task that had little measurable business impact; and decided to layoff the engineers instead. Google is a wealthier company, but they may decide that trying to "fix" C++ is a more realistic endeavor.
- tialaramex 4y agoRust is mentioned in the blog post. Firefox has a whole bunch of Rust in it, much more than it did when that blog post was written. Mozilla let go some core Rust people, but of course it still employs numerous Rust programmers.
- dblohm7 4y ago(Former Mozilla employee here) Rust is encouraged to be used by all developers who are writing native code (it's not supposed to be just the domain of a select few). As I recall, the message was, "Not knowing Rust must not be an excuse for why you're not using Rust." Of course developers must still contend with the practicalities of integrating Rust into an existing C++ codebase, so some guidelines were developed: https://wiki.mozilla.org/Oxidation https://wiki.mozilla.org/Oxidation
- anonymousDan 4y agoI don't know, maybe it's the sadist in me but I enjoy the challenge of writing fast and correct multi-threaded code. It's a valuable skill if you can master it.
- layer8 4y agoYou mean masochist? ;) I’m similar, though I wish programming languages would gives us more facilities to statically double-check the correctness reasoning.
- oriolid 4y agoMasochist? Think about the poor programmer who has to maintain GP's code :)
- tanyar 4y agoThis is the way. That code breaks when someone new comes along and makes several modifications that has implications which take a long time to rear their ugly head. Broken multi-threaded code behaves a lot like perfectly coded multi-threaded code....until you get a lot of traffic through that code, you know, like on a busy holiday weekend when lots of revenue is flowing through the business!
- oriolid 4y agoExactly. And you don't always need a lot of traffic, so even if your code survives load testing doesn't mean it's correct. Just last week I ran into a serious problem with a piece of code that handled infrequent events from several different threads, and an almost unrelated refactoring changed timing so that events arrived from two different threads exactly at the same time, on exactly one family of Android phone models. The original mistake had been in production for a long time without anyone noticing.
- hardlianotion 4y agoNah. Parent is thinking about users.
- ghufran_syed 4y agoI have very little experience of writing concurrent code, but this seems relevant: https://clojure.org/about/concurrent_programming https://clojure.org/about/concurrent_programming
- nlewycky 4y agoI don't have a photo but we used to have the same sign at Google, with an added note "unless accompanied by ThreadSanitizer". At runtime: https://clang.llvm.org/docs/ThreadSanitizer.html https://clang.llvm.org/docs/ThreadSanitizer.html At compile-time: https://clang.llvm.org/docs/ThreadSafetyAnalysis.html https://clang.llvm.org/docs/ThreadSafetyAnalysis.html The article points out that that: The resulting invariants end up being documented in comments: // These variables are protected by monitor X: but you can write those in C++ code and have the compiler check them. That's what thread safety annotations are for.
- kubanczyk 4y agoTIL that, so predictably, "[Go's] race detector is based on the C/C++ ThreadSanitizer runtime library". So, I can definitely confirm its usefulness. https://go.dev/blog/race-detector https://go.dev/blog/race-detector
- GuB-42 4y agoI use Valgrind's helgrind. In fact, Valgrind is generally my favorite debugging tool: memcheck (default) for memory errors and leaks, callgrind for profiling, massif for memory profiling and finding "stealth" leaks, helgrind for multi-threading and a few others that I use less often. The great thing about Valgrind is that it doesn't require any instrumentation at build time (though debug symbols are recommended), just run "valgrind your_program". Behind the scenes, it is actually a VM, that's how it can work with binaries directly. In theory it works with any language, and no need to do anything funny with libraries, kernel stuff, etc... The biggest problem is that the performance penalty is huge, 10x is typical but 100x is not uncommon. ThreadSanitizer is slow but not that slow, I don't know which one is the best at finding issues, I think they are on par, but when you are an a particularly hairy problem, it is good to have both.
- wallstprog 4y agovalgrind is great, but be aware that valgrind runs single-threaded, which definitely changes the way multi-threaded apps behave: https://valgrind.org/docs/manual/manual-core.html#manual-core.pthreads_perf_sched https://valgrind.org/docs/manual/manual-core.html#manual-cor... In practice what this means is that cpu-bound threads can monopolize the single thread, and we've had thread starvation issues as a result in some tests. Setting the "-fair-sched" option resolved those issues, but made valgrind run even slower ...
- new_stranger 4y agoAssuming you don't need bare-level performance and Go is okay, I love how easy and approachable parallel code is with Go. Locks and channels each offer trade offs you can easily benchmark using the built in go test. I've written so many different concurrent approaches to workers, pipelines, and multi-stage processing systems and found it always easy to reason through ways to limit the number of workers, prevent duplicate work, and implement backoffs.
- masklinn 4y agoGo is full of concurrency pitfalls: https://eng.uber.com/data-race-patterns-in-go/ https://eng.uber.com/data-race-patterns-in-go/ It makes concurrency easy, it doesn’t make concurrency correct easily.
- xigency 4y agoI don’t really support this view. While doing anything creative with threading is in fact challenging, in Java it’s easy to safely boost performance with threading using parallel streams and concurrent data structures. Even in other languages like C/C++, it’s mostly worth it to design something up-front that is naturally parallelizable and find a safe and correct way to do it. And the incentive to parallelize is only increasing with the increasing thread counts of newer hardware. Sure, it’s scary, but there’s really no excuse to avoid running with threading, multiple processes, or multiple services.
- jayd16 4y agoIn my experience I saw a bunch of novice devs using parallel streams for N<=100 lists and such, for much worse performance. Its certainly not foolproof either or it would just be the default.
- marginalia_nu 4y agoTo be fair, whether parallel streams is useful isn't a function of N alone, but the time each operation takes. If it's a nontrivial calculation, then by all means. Although parallel streams are seldom the best option in terms of performance even for large N. They may often be faster than sequential processing for large N, but a more thought out approach is almost always faster still, often significantly so.
- muglug 4y agoI'm glad that Rust has changed this, at least in my very specific case: I wrote a command-line application in PHP and adding parallelisation was a long journey of trial and error and concurrency bugs that took over a week to get right. I rewrote the same application in Rust. I followed the Arc/Mutex documentation and parallelised it over a few hours. It worked perfectly.
- marcosdumay 4y agoIt's hard to overstate how much easier it is to do multi-threaded code in Rust compared to other languages. But it is still not easy in absolute terms. It is still a task many developers aren't able to do.
- muglug 4y ago> It is still a task many developers aren't able to do. Yeah, I think parallelism is naturally intimidating to many developers used to single-threaded work. I just those developers encounter Rust.
- dang 4y agoDiscussed at the time: Must Be This Tall to Write Multi-Threaded Code - https://news.ycombinator.com/item?id=9905374 https://news.ycombinator.com/item?id=9905374 - July 2015 (112 comments)
- YesBox 4y agoI recently wrote some multi-threaded code (for the first time) for a game I'm working on. Luckily the data I need to process is in a (C++) vector, so I was able to break the data up by dividing the vector size by available cores. I certainly felt 10 ft. tall when it worked! Another area of my code where concurrency will greatly benefit it requires updating an unordered_map. Since it's positioned based and not unit_id based, I'll need to figure out a way to prevent data races. The best option I've thought of so far is assigning thread numbers to each edge in my graph, and storing the units in each edge in a queue, which should prevent data races and car collisions. (edit: ha) That being said, I'm the sole developer on this project and I cannot imagine working on a multi-threaded application with dozens of other developers. It is nice dipping my toes in the water though.
- jcelerier 4y agoThere are multiple concurrent map implementation available - the most known one in C++ is certainly TBB's : https://oneapi-src.github.io/oneTBB/main/tbb_userguide/concurrent_hash_map.html https://oneapi-src.github.io/oneTBB/main/tbb_userguide/concu...
- YesBox 4y agoNeat! Thanks, I will look into this
- SleepyMyroslav 4y agojust my 2c Friday rant on old article. Games had to adapt to threading before I even joined. I got lucky to work with some very efficient threaded systems. They were never correct or safe xD. But I can share that if you see comment that these variables are protected by this lock more that few times per million of lines of code performance is not the reason why it is threaded. From public videos i think best explanation is available from Sean Parent "Better Code: Concurrency" [1]. I really would like to see high level or safer languages or systems to come to future game platforms. It should be possible. It just needs economical factors to kick in. Like people finally notice that slow and safe is not going to be any faster next year with new hardware. 1. https://youtu.be/32f6JrQPV8c https://youtu.be/32f6JrQPV8c
- buybackoff 4y agoI would change to edit MT code. It usually starts with a clear picture in mind... But even one's own code quickly becomes cryptic without lots of comments and hidden assumptions. I used to have this picture printed and put above a server in a small startup-like team. The whole thing ended not related to any lock or interlocked call though.
- kcl 4y agoI've written on this problem before: https://kevinlawler.com/parallel https://kevinlawler.com/parallel One issue is that the pthread model is too low level (think AND and OR gates), but you must include it for completeness. I agree with the comments here that say higher-level multithreading support needs to be "baked in" to the language. Grand Central Dispatch did a good job of raising the bar here, but has its own problems, and is arguably too low level. Another issue is that existing, widespread languages are too old to have cooked higher-level threading support in. You really have to do it from the start. C++ bolt-on parallelism might as well be syntactic sugar. Newer languages, like Rust, have attempted to solve concurrency problems but failed - it's not clear that the designers genuinely understand concurrency at all. Goroutines solve one limited issue of many and seem about as difficult as asking programmers to write threaded programs. To do this correctly you need a new language that cooks it in from the start. There is a laundry list of things that have to be done precisely. It might be possible to do this by rewriting a reference implementation of an existing popular language, but the development overhead and performance concerns involved in backwards-compatibility make that probably infeasible. (Python can't even escape it's global-interpreter lock.) So likely the language that solves this problem is either unwritten or unknown. Other mentions in the comments here: ThreadSanitizer: It's not very good, nor can it be. Erlang: solves the problem, but the actor formulation is too awkward for all but Erlang-specific use-cases.
- waynesonfire 4y ago> Erlang: solves the problem, but the actor formulation is too awkward for all but Erlang-specific use-cases. Play with Erlang a bit and you'll find the modern band-aid pattern of async / await awkward.
- lostcolony 4y ago>> Erlang: solves the problem, but the actor formulation is too awkward for all but Erlang-specific use-cases. Err? I mean, it requires rethinking a bunch of the lessons you learn when concurrency is hard and expensive and to be minimized, sure. But when concurrency is easy and cheap and to be embraced, a bunch of things become a lot easier and more natural to write. My go to example, from a production system, was a complicated task scheduling system (with user and hardware interrupts to handle). Sure, we could have written it using threads and a priority queue or something...instead we just had one Erlang process per task, with interrupts routed to that process as messages (and linked timer processes that fired messages to those processes for the scheduled stuff, essentially just another type of interrupt/message). Super simple to write, super simple to reason about, never had any sort of race condition or locking issues, across years of development and production use.
- kerblang 4y agoMost of us work in information systems, which is to say, doing database stuff all day, usually with an RDBMS like MySQL, Postgres etc. And what are we doing? Grabbing a bunch of transactional locks. Against what? Shared mutable state, and not in-memory state, but database state counting into the thousands, millions, billions or more records. And if we do it wrong? Deadlock, often deadlock across multiple processes. And what do we do about that? We deal with it, because what else can you do? You could of course throw transactions away and declare them more trouble than they're worth, but that probably won't go over well. I've found it helps to have a "consistent first lock", so for example in a consumer-based system you'd lock the customer record first, because most transactional operations happen in that context. If you always have that consistent first lock then deadlock can't happen. My point is that if I assert "multithreading is unacceptable!", most of business information systems goes out the window on the same principle, because the locking is even more dastardly - multi-process and persistent state instead of in-process in-memory state. I don't think you could throw actors/schmactors/coroutines/goroutines/etc at such usefully either. And if you said "Well only the best programmers need apply," well BIS is not exactly hotshot heaven, by and large. It's a bunch of grunts. So I agree multithreading is hard when you are locking many things at once, which I try to avoid. But I just don't get this thing of trying to banish it or make it the exclusive domain of geniuses.
- kaba0 4y agoDatabases (and I would argue runtimes, compilers, etc.) are written in a much much higher quality than your regular CRUD webapp, by usually much more experienced developers, with proper architectural plans and they are sometimes even model checked by TLA+ or the like.
- kerblang 4y agoThat's beside the point, as I'm not talking about RDBMS bugs. I can corrupt database data when the RDBMS is working exactly as it's supposed to. Strong types and various constraints like foreign key, unique, etc. will mitigate, but there's infinite ways to make a mess beyond that.
- robocat 4y ago> Buggy multi-threaded code creates race conditions, which are the most dangerous and time-consuming class of bugs in software. > safe and scalable parallelism is achievable by minimizing or eliminating concurrent access to shared mutable state. > Rust is designed from the ground up to facilitate this Does async code with mutable state sometimes result in race conditions in rust? I have had that experience with JavaScript. For example old school short timeouts with a function written with a closure implicitly expects the state to remain the same but a race condition happens asynchronously to change the state, and then the closure is called, and then the closure code fails. A problem of unintuitive surprise to the programmer that can happen with async code (even without thread concurrency).
- pornel 4y agoNo, you can't accidentally modify shared state in Rust. Unique ownership requires making sharing explicit, and protections against data races force shared mutable data to explicitly use synchronization primitives like mutexes or atomics. This applies to everything, including closures and async code. It's not entirely foolproof. You can still have higher-level logic bugs, race conditions when accessing external resources (files, network, database), and deadlocks. However, these tend to be easier to track down and reproduce than heisenbugs caused by data races from unsynchronized memory access.
- bee_rider 4y agoJust sprinkle in some OMP PARALLEL DO's it'll be fine.
- 3flp 4y agoIn my world of embedded single core processors, we use "multi-threaded" code all the time. There is a single core so nothing truly ever executes in parallel and that makes it easier: Someone else already wrote the RTOS, and figured out how to prevent priority inversion, etc. So long as one follows a few rules, and the general pattern is tasks and ISRs communicating via the primitives provided by the RTOS, there are never any issues.
- imtringued 4y agoThe embedded world has interrupts to worry about.
- juliangamble 4y agoThis debate doesn’t mention software transactional memory which as a programming model, in languages like Clojure and Scala and Haskell has a much lower cognitive overhead for multi-thread scenarios. Ie it is much easier to ensure you have avoided deadlocks using STM.
- stephc_int13 4y agoHere is a technique I use to write multi-threaded code without too many bugs. I write sequential code, where most heavy work is arranged to fit in arrays and processed with a parallel_foreach function receiving a container and a worker that will be dispatched by the job system that is part of my framework. The multiple cores are used mostly to accelerate the processing of heavy and linear tasks while the control flow is still purely sequential. This is not optimal, but I think this is a reasonable tradeoff between simplicity and performance.
- samsquire 4y agoI implemented multiversion concurrency control and a poor but safe lock free algorithm. I am still coming up with rules. As the Serializable Snapshot Isolation paper attests, you can avoid dangerous 2 reads and writes to the same data structure with an algorithm (in the paper) with timestamp ordering and trying to detect cycles with a heuristic in the graph with two booleans inConflict and outConflict. The answer to concurrency bugs is that languages implement MVCC. See my ideas4 page for concurrency control ideas. Most software doesn't scale with threads and doesn't offer true parallelism. In other words programs don't use threads to their full potential. I have an idea for LMAX disruptors with a multiproducer multiconsumer ringbuffer in ideas4. Don't block the event loop! I like process calculus and pi calculus and CSP. But I think we can combine them altogether. Universal constructors and work stealing and compare and set is a powerful primitive for concurrency. HTTPS://GitHub.com/samsquire/multiversion-concurrency-control HTTPS://GitHub.com/samsquire/compare-and-swap-sort HTTPS://GitHub.com/samsquire/ideas4
- anonymoushn 4y agoI read this recently and found it weird that it ends by saying Rust enables message-passing. It sort of seems like the opposite. Rust will fight you to the death to ensure you don't have concurrency bugs even in single threaded applications or applications in which threads only communicate by copying data into and out of queues. When you do copy the data, it will insist that you use its global allocator containing synchronization primitives instead of a per-thread allocator.
- rawoke083600 4y agoI old but not too old, I was lucky(unlucky ?) enough to still had to write C++ mutli-threaded code in my first job. No matter how good I thought (spoiler, I wasn't), many a bugs could have been traced back to my erm "understanding" about what is safe and proper threaded code. If you have to do things in parallel or concurrent (lol calm down now HN, I know there is a difference). I'd say just go look at GO ! Damn once you get the hang of channels and CSP concept. It's so beautiful and elegant ! :) I'm sure there are many counterpoints and it doesn't fit every problem, but as a professional cs practitioner, you owe it to yourself to give Go and it's paradigms a fair and open mined go ! Oh and the built in race-detector is glorious tool ! The "feeling" you get when you got a zillion fibers/channels going and the race detector reports no issue is like "levelling up" ! Sorta how if you serious about CS/Coding you have to at least install a LISP once in your lifetime - even if you do nothing with it but swear and squint :) #duck !
- mbrodersen 4y agoWriting multi-threaded code is easy if you use semaphore protected queues between threads as the only way for threads to exchange data. Basically emulating how Erlang does it.