Software Transactional Memory (1997)
dl.acm.org
dl.acm.org
https://wiki.haskell.org/Software_transactional_memory
As I always say, Haskell is the Mercedes of programming languages.
"The Clojure STM uses multiversion concurrency control with adaptive history queues for snapshot isolation, and provides a distinct commute operation."
https://clojure.org/reference/refs, for the curious and brave
Maybe others have had a different experience, of course. This is just an observation about my own code and the libraries I use.
It turns out that updating a single storage point in an atom with a CAS serves 99.9% of use cases, and is much, much simpler than ref-based code.
Haskellers (including myself) tend to default to STM to take advantage of atomicity.
Of course it's easy to make the safe choice when the shared interface between concurrency primitives make switching very low cost.
More: https://www.oreilly.com/library/view/parallel-and-concurrent...
Comfortable, packed with features, pretty fast given its weight, kind of elitist yet approachable if you invest time in it? :p
Also something about inventing/spurring mass adoption of airbags and ABS. Safe and fast.
What this basically does is generate multiple versions of functions that can be called both in and not in a transaction. Functions that may be called in a transaction have to be side-effect free (i.e., no system calls, recursively). The modified functions run code for each line of memory read or written so the STM engine can decide what to do with it (e.g., reads can be redirected to other locations, writes can be used to dirty memory or redirected to a scratch location).
There are different trade-offs for efficiency, conflicts, etc. For instance, if each transaction begin just started with a global exclusive lock it would be correct, simple, and you'd never get conflicts, but just serializes everything. There are trade-offs.
Languages with more rigid semantics and a propensity towards immutability would require less annotation. The GCC version generates these invocations for engine functions on most loads and stores. As you'd imagine, it's pretty expensive as is and basically is not used as a generic method of concurrency control for this reason.
Here is gcc's doc on libitm https://gcc.gnu.org/onlinedocs/libitm/ Here is intel's document (much more useful): https://gcc.gnu.org/wiki/TransactionalMemory?action=AttachFi...
I'm not sure what the support story is like on clang.
Then I'm not sure what happened, things seem to have run out of steam, Intel never managed to make its HTM to work well and it unofficially deprecated it and generally TM went out of fashion. The TR is still there and gets minor updates, but I haven't heard about any push to merge it.
I had spent most of the 90s working with transactional systems and while they are useful and powerful, they aren't magic and require awareness to keep from making errors. Joe Duffy wasn't wrong when he says those problems are well known. The solutions normally are either Don't Do That Then, or managing the policy details and logic yourself, which wasn't giving them the easy concurrency they were trying for.
Edit: He has a followup post at http://joeduffyblog.com/2010/05/16/more-thoughts-on-transact...
After reading them both, I asked this unanswered question on SO: https://stackoverflow.com/q/72084071/582917. My theory is that STM is the same as SI, but most SI database implementations don't just do value comparisons, but actually check a logical timestamp. This is probably done for performance reasons as databases handle larger pieces of data than functions would when using STM.
Along the way I also discovered SSI serializable snapshot isolation but it isn't yet available in rocksdb but cockroachdb apparently has a fork of rocksdb with it but I couldn't find it.
Anyway the db library which wraps around rocksdb is available to be used embedded in any nodejs program at https://github.com/MatrixAI/js-db.
MVCC implementations are indeed fundamentally about concurrency, but they also tend to make snapshot isolation easy to implement. (Far easier than implementing snapshot isolation with classic pessimistic locking!)
But I don't think they used STM approaches to do it. So STM works nicely inside languages like Haskell, but when you work with a library database you can gain similar behaviour by using a key value database that has SI.
What STM offers is an easy way to invent "containers for snapshotted values" aka TVars. Using them carefully may result in better scaling: https://hackage.haskell.org/package/stm-containers
JavaScript is single threaded so I cannot see how you can implement actual concurrency control.
I implemented multiversion concurrency control in Java but I am yet to include the algorithm from the whitepaper Serializable Snapshot Isolation.
https://github.com/samsquire/multiversion-concurrency-contro...
But even in single threaded JS, asynchronous handlers can result in concurrent race conditions. That's why concurrency control is necessary. I also maintain https://github.com/MatrixAI/js-async-locks repo to help control all sorts of concurrent effects.
You can create your own libuv event loops and run them on a separate pthread.
If you create a pthread in your own C++ extension then you're not really talking of Javascript.
Workers are not threads but they run in their own thread. Unfortunately you don't have shared memory. So you kind of have snapshot isolation by immutability. They can only communicate with postMessage. They're not threads, they do not share address space.
So Javascript is single threaded.
If you're getting race conditions with your multiversion concurrency control implementation, then you might need to look at how you isolate versions from one another. Write a unit test that adds numbers from 1 to 100 from 100s of concurrent transactions, you should end with 100 every time if you have no race conditions.
Without parallelism, you could introduce a bulk command API and wrap all your writes and reads in a single function.
The single threaded nature of Javascript will prevent race conditions if you do this - only one function can run at a time.
But I'm not talking about concurrency control between threads, but between asynchronous operations.