10 ms·
Safely writing code that isn't thread-safe
- p1mrx 4y agoMirror: https://web.archive.org/web/20221123004412/http://cliffle.com/blog/not-thread-safe/ https://web.archive.org/web/20221123004412/http://cliffle.co...
- 1letterunixname 4y agoFor a built-in reference semantics system that's finer-grained than Rust's, there's Pony's system (has a GC, but Orca is supposedly faster than C4 or HiPE/BEAM). https://tutorial.ponylang.io/reference-capabilities/capability-matrix.html https://tutorial.ponylang.io/reference-capabilities/capabili...
- smallstepforman 4y agoI’d love to use Pony (sans GC), but the project is dead, Sylvan has lost interest, Sebastian has lost interest, and even its biggest advocates (Sean and Joe) have moved on. It’s s shane since I really really like what Pony was doing.
- izietto 4y agoAre you sure about that? I see movement in the main repository and the latest release dates to 9 days ago: https://github.com/ponylang/ponyc/releases/tag/0.52.1 https://github.com/ponylang/ponyc/releases/tag/0.52.1
- ergl 4y agoThe project is definitely not dead! Right now it is entirely volunteer-driven, so the amount of work being done depends on the amount of work put in by the overall community. Feel free to drop by our Zulip to catch up on the latest developments: https://ponylang.zulipchat.com/ https://ponylang.zulipchat.com/
- ajross 4y agoIt takes a while to get to it, but the point of the argument seems to be that naive Rust code can't have race conditions because naive Rust code will only assign to memory through a mutable reference, and the borrow checker guarantees that no two functions (no matter which thread they may be running in) can have borrowed such a reference simultaneously (whether "concurrently" or not). And that's true as far as it goes, I guess. But the flip side is that naive Rust code just isn't useful for multithreaded work and tends to require heavier abstractions (e.g. copy "commands" or whatever in and out of queues vs. just taking a lock around some shared data). I occasionally try to look at the natural way one expresses the kind of stuff we do in Zephyr in a Rust kernel, and... there's really not much there besides copious and extensive use of unsafe.
- nyanpasu64 4y agoIn my experience multithreaded Rust isn't substantially more restrictive then multithreaded C++ at writing code free of data races (although it very much is at writing single-threaded code free of use-after-free). The primary methods in Rust to share memory across threads (in my experience writing high-performance wait-free but userspace not kernel code) are custom UnsafeCell wrappers which implement Send and Sync and have safe locking mechanisms preventing data races, safe atomics, or safe mutexes.
- the_mungler 4y agoTo be fair, rust at kernel level seems to have way more unsafe code than usual, though I have no experience in that area so I can't say much. In rust you absolutely can "just take a lock around some shared data", but it is a bit different then other languages. mutexes actually contain the data they guard, making it impossible to modify unless you have the lock. There is another article about muexes by this same author if you want more details: http://cliffle.com/blog/rust-mutexes/ http://cliffle.com/blog/rust-mutexes/
- School-Cotton 4y ago> just taking a lock around some shared data Can’t you do this in Rust with std::sync::Mutex and similar?
- aidenn0 4y agoI spent the first 10 years of my career thinking I wasn't smart enough to write multi-threaded C code. Since then I have been convinced that nobody is smart enough to write multi-threaded C code, and multiprocessing should be used instead.
- jjgreen 4y agoTosh, a lot of use-cases need shared read-only access to a large structure, mark that const* and you can thread away without a mutex in sight, each thread has it own r/w struct to accumulate results.
- aidenn0 4y agoI agree, and with multiple processes that's just a single mmap() call away!
- CarVac 4y agoMultiprocessing as in openmp?
- yazaddaruvala 4y agoThe parent is using multi-processing to mean: “no shared memory concurrent processing” Which still isn’t quite good enough because files (especially mmaped files) can be abused as shared memory even between multiple processes. But the general point is good, “you’re better off with mutable xor shared memory while concurrently processing”.
- aidenn0 4y agoRight process aren't "shared nothing" but at least it makes you work a little bit before you can shoot yourself in the foot..
- aidenn0 4y agoSorry, the other reply explained what I meant. "Multiprocessing" has many meanings but specifically when contrasted with "multithreading" it usually implies the thread/process distinction in the Unix sense of the terms.
- samsquire 4y agoThank you for writing this. I am deeply interested in parallelism, asynchrony and multithreading. I blog about it everyday in ideas4 (see my profile) I try think of automatic parallelization approaches. And how to structure programs that take advantage of queuing theory and processing ratios. Imagine if the compiler could work out that one program has a ratio of processing of 2:1 then it could scale out automatically. I am very interested in compilers that can automatically parallelise. I find Pony to be very interesting with its reference capabilities. My problem with Rust is that is precludes many safe programs. References can be alive and be passed around. Regarding bank account transactions, I worked on the same problem. It lead to me implementing multiversion concurrency control. Multiversion concurrency control allows thread safety avoiding locks except for internal data structures of the MVCC. Multiple threads can read/write the same data but they'll never modify the exact same data due to the multiple versions. https://github.com/samsquire/multiversion-concurrency-control https://github.com/samsquire/multiversion-concurrency-contro... I also implemented the bank account example but I serialised the account transactions to the same account numbers in ConcurrentWithdrawer.java - this solution doesn't use multiversion concurrency control. My other solution BankAccounts2.java and BankAccounts3.java take different approaches. The BankAccounts2.java has 1 thread out of 11 threads that synchronizes the bank accounts and performs the serialization of account balances. The other threads generate transactions. BankAccounts3.java simply uses a lock. The problem with the bank accounts problem is that I am yet to work out how to scale it. You could shard threads to serve a certain range of account numbers. Or you can separate the current balance of each account across threads and if the receiving transaction is less than that balance, you don't need to read other threads to check. I recommend this article by Vale dev's https://verdagon.dev/blog/seamless-fearless-structured-concurrency https://verdagon.dev/blog/seamless-fearless-structured-concu... I also experimented with other concurrency primitives. One is a scheduler that receives requests to write to shared memory. Then it schedules the request then the thread marks a callback array and the next request can be served. There's scalability challenges.
- mrkeen 4y ago> I also implemented the bank account example but I serialised the account transactions to the same account numbers in ConcurrentWithdrawer.java - this solution doesn't use multiversion concurrency control. Damn that's a lot of code. Mutability everywhere, nulls and special integers (-1), triply-nested for-loops, direct indexing into lists. And it's all in memory? If you're in a situation where you want multiple safe writers, just use software transactional memory.
- cozzyd 4y agoHow does rust implement signal handlers?
- jakewins 4y agoCrates like signal-hook force you to use APIs that can't interact directly with the rest of your apps state, I think: https://crates.io/crates/signal-hook https://crates.io/crates/signal-hook
- pjmlp 4y agoNow do the same exercise talking to a real database over distributed computing IPC mechanisms, while ensuring everyone else is following the same rules, regardless of the programming language talking to the same database tables.
- dxuh 4y agoThough I have struggled with this quite a few times, I have never considered that preventing your code from being called from multiple threads is a great feature to have. I have written comments stating that certain functions should not be called from multiple threads many times. And I myself have called these functions from multiple threads by accident before. Just another comment about this: > Basically all languages except C(++) have an async mode that transforms code into state machines that can be interleaved on a single thread of execution. C++ has coroutine support since C++20. It's not pretty, but it's there.
- lll-o-lll 4y agoNot pretty! How dare you! Beauty is clearly in the eye of the beholder.
- masklinn 4y agoOr the numerous eyestalks of the Beholder, as the case may be.
- throwaway17_17 4y agoI know meta-commentary on comments are slightly frowned upon, but yours just made my morning. So sincerely, thank you.
- nly 4y agoC++ coroutines are incomplete without a an executor to run them on. Unlike C# you can't just sprinkle in async and await keywords and let the background thread pool take care of it...and doing so would be undesirable, given the lack of thread safe garbage collection. Boost.Fiber on the other hand pretty damn cool.
- steveklabnik 4y agoThe exact same thing is true of Rust.
- 4y ago