Virtual Serialization: when Two Threads Become One

Antonio Alexander
4 min readJan 27, 2023

Virtual Serialization is the idea that if two threads are dependent on each other, they can become one/serialized which has far reaching implications when it comes to performance, maintenance and readability. We may be asking ourselves…why don’t we just ensure that our code only uses one thread?…but then we may not be able to meet certain requirements such as performance, maintenance and readability without some form of concurrency, parallelism or abstraction. These kinds of tools allow us to design software that can be described by some cool acronym like ACID or SOLID as well as multi-threaded code that is honest and collaborative.

With great functionality comes great complexity, learn with me, let’s dive into the idea of virtual serialization and how to mitigate and resolve unexpected failures by applying synchronization. Although there are plenty opportunities to dive into code, don’t expect any of that here (sorry). Feel free to ask me to give a more concrete example.

Virtual serialization is a function of your business logic, it isn’t always something you can completely avoid, and as a result you should take any necessary serialization into account when architecting your application. Failures can occur when a process isn’t properly modeled or protected. [Some] Race conditions are indications of this kind of failure, but you may find that your logic or state machine can’t become stable even if the inputs aren’t changing. Although these failures are definitely high-stakes when it comes to control systems, oftentimes, the results of these race conditions are far more innocuous but still a ticking time bomb.

We’ve already established that there is a need to run the code in multiple threads, but by running them in multiple threads, they’re linked through the business logic which makes one dependent on the other; again we also KNOW that the perfect modeling of the process is to bring them together in a single thread but we CAN’T. In these cases, the dependency between the threads come when they attempt to share “data”. Data can sometimes be simple, like one or more variables, but it can also be more complex in that they share a “process”.

Synchronization tools such as mutexes, queues, interrupts/one-shots etc., can be used to ensure that shared data and/or processes can only be interacted with by a single thread at a time. This has obvious implications for performance (sometimes) in that the other thread(s) may be blocking while another is interacting with the protected data/process. Performance aside, the failure modes are often more important than the degradation in performance; for example: “Is it more important that your bank deposit shows up sooner or that it shows up correct?” Although banks protect data (your money), they also have to protect each process (transaction). If you were to deposit and withdraw simultaneously, depending on the timing and what the balance is when each of those operations could occur, your balance could differ wildly (if you didn’t use any synchronization).

These are some of the tell-tale signs that something isn’t quite right when it comes to your application and how you’ve modeled/architected the process:

  • Your process isn’t very performant when it should be
  • The output of your process is different even though your inputs are the same
  • Your process(es) take “longer” than you expect them to
  • The performance of your process is inconsistent

When designing your application/architecture around business logic, it’s important to understand the idea of concurrency and ensure that when important logic runs, synchronization protects OTHER concurrent logic from interacting with it in a dangerous way (generally avoiding mutations). When you can identify this process, you can ask questions such as:

  • How many times must my logic run until a certain state is reached?
  • How much time until this logic realizes that “something” has occurred?

The answers to these questions help define how to model processes, how to validate them and finally how to characterize how those processes should execute at runtime.

If it’s not already obvious, this is another way of me telling you to ensure that your code is as Honest as possible. Our code can only do what we tell it to do, and for it to do that well, we have to be certain that we fully understand what we’re trying to do. Honesty, especially within programming, can’t exist from a place of ignorance; it’s better that your code doesn’t work at all, than it working just because you’re lucky.

I want you to look at your code like Anthony Mackie is looking at you right now. A loving but wry smile, honest, but certainly not functional due to a roll of the dice. When trying to make the decision as to whether performance, maintenance or readability is worth going the added complexity of concurrency; you should now have a little more information to help you choose one or the other.

Take some time, be introspective about the code you’ve written; feel free to ask me my opinion. We don’t work for free ‘round here, but I’m happy to stand on my soapbox if it makes you a better programmer.

--

--

Antonio Alexander

My first love was always learning (and re-learning); hopefully I can share that love with you.