3 ms·
I've run into something like this when working on embedded systems using OO. You have persistent mutable data, you have an object that encapsulates some data,
by AnimalMuppet 2y ago
I've run into something like this when working on embedded systems using OO. You have persistent mutable data, you have an object that encapsulates some data, you have multiple sources of control that each have their own threads that can modify that data, and you have consistency relationships that have to be maintained within that data.
The way you deal with that is, you have the object defend the consistency of the data that it controls. You have some kind of a mutex so that, when some thread is messing with certain data, no other thread can execute functions that mess with that data. They have to wait until the first thread is done, and then they can proceed to do their own operations on that data.
This has the advantage that it puts the data and the protection for the data in the same place. Something "way over there" can still call the function, but it will block until it's safe for it to modify the data.
(You don't put semaphores around all data. You think carefully about which data can be changed by multiple threads, and what consistency relationships that could violate, and you put them where you need to.)
Is that better or worse than Erlang's approach? Both, probably, depending on the details of what you're doing.
- jerf 2y agoThat's possibly what I meant by the "actors that don't get their own thread" at the very end. I've switched away from Erlang and write most of my stuff in Go now, and while I use quite a few legit "actors" in Go, that have their own goroutine, I also have an awful lot of things that are basically "actors" in that they have what is effectively the same isolation, the same responsibilities, the same essential design, except they don't actually need their own control thread. In Erlang you often just give them one anyhow because it's the way the entire language, library, and architecture is set up anyhow, but in Go I don't have to and I don't. They architecturally have "one big lock around this entire functional module" and sort of "borrow" the running thread of whoever is calling them, while attaining the vast majority of benefits of an actor in their design and use. If you have an "actor", that never does anything on its own due to a timer or some other external action, that you never have to have a conversation with but are interacting with strictly with request-response and aren't making the mistake someone discussed here [1], then you can pretty much just do a One Big Lock and call it a day. I do strictly follow the rule that no bit of code ever has more than one lock taken at a time. The easiest way to deal with the dangers of taking multiple locks is to not. Fortunately I do not deal in a performance space where I have no choice but to take multiple locks for some reason. Though you can get a long way on this rule, and building in more communication rather than locking. [1]: https://news.ycombinator.com/item?id=41722440 https://news.ycombinator.com/item?id=41722440
- mrkeen 2y ago> You don't put semaphores around all data You're talking about putting semaphores around code. Locking data, not code, is a great way to do things. It composes and you don't run into too-many-locks, too-few-locks, forgetting-to-take-a-lock, or deadlocking problems. https://www.adit.io/posts/2013-05-15-Locks,-Actors,-And-STM-In-Pictures.html https://www.adit.io/posts/2013-05-15-Locks,-Actors,-And-STM-...