Can you remember the last time you have built a sandcastle? It was probably some time ago. I can imagine the scenery, you and your parents together on a summer vacation trip to the sea-side. This day was supposed to be special, it was a very first weekend of your two-months break from school, it was warm and sunny and the World was beautiful. But swimming in the sea and having a sun bath wasn’t that all, your parents promised you a visit in a Car Museum, waffles and big, big ice-cream. You couldn’t wait for this day to come.
Synchronous Model
At last, the day has come. Saturday morning, first day of vacation. Your mom came to woke you up at 8 a.m., but you were already awake, you knew what was the day that day. You have already packed your staff to your favourite bag pack, there was a towel, trunks, “Treasure Island” to keep your mind busy during a car ride. You also had your drone with camera, so you can catch your best moments when swimming or playing beach ball with your mom. You came to the kitchen for the breakfast and everybody was already there. After quick breakfast, you got in the car and drove straight to the “Sandy Beach”.
When you reached your destination, it turned out that many people had the same idea as you did, the beach was full of people working on their tan and cooling themselves by swimming. You were lucky enough to find some place to put your towels and your dad asked if you want to build sandcastle. You and your mom agreed, so you went closer to the coast and started a building process. Now. let’s stop for the moment and think about synchronicity in terms of programming.
What It Takes To Get Yourself To The Beach
Let’s rewind events to the moment you woke up. From that particular moment in time up until the moment you have entered the beach, it took some activities to be made and some time to be passed, to get you folks there. First, you had to wake up - admit it, it’s hard to move your body when you sleep (check if not a sleepwalker). Next you move to the kitchen and have some breakfast. Second, you get into the car, public transportation, or you take a walk if the distance is not too far, but this time, the car was a right choice. You have packed your things, closed the door and of you go. Third, a proper distance must be travelled, so you can finally park your vehicle near the beach entrance and finally reach your destination.
Synchronicity In Programming Means Sequential
What we did in the previous paragraph, we defined three major steps required to fulfill a trip. Waking up, put yourself in some transport and travel from point A to point B. Now think, does it make any sense if you have started a whole thing from the travel process itself? Is it feasible to move without walk or put yourself to some vehicle first? Or even before you wake up? The answer is quite short - it’s not doable, and it doesn’t make any sense. There must be an ordered sequence of events for something bigger to happen. But there is something even worse.
Synchronous Programming Is Blocking Kind
Every activity which implements a Synchronous Model, needs as much time to complete as it takes for all sub-activities to complete. Let’s do some math:
- waking up and breakfast - 1 hour
- getting into the car, packing your staff and visiting a gas station - 0.5 hour
- driving to the sea-side, finding a place to park the vehicle - 3 hours
After summing everything up, it turns out that 4.5 hours are needed to finish the task, to reach the goal for the day. There is no other way to shorten this but try to pack faster or drive beyond speed limits. Every step requires the previous one to be completed before it can even start. What’s even worse, you are literally blocked when performing each of the activities, because it’s quite impossible to do anything what would make the progress faster. You can read the book when having breakfast, make a phone call to a friend when packing your bag or writing down your memories during the ride, sure. Unfortunately it doesn’t make things faster.
The same goes with a Synchronous Program when it’s being executed. Instructions you implement run in the exact same order as they were written. Every block of code, every function or procedure will have to finish its execution, before the next part of the program can be started. The process or threads created by this executable will be blocked until current jobs are not finished successfully.
And just as you are blocked when sitting in the car, the execution of your application is also halted on a current line of code. Now, this operation can be anything - from simple variable assignment, to iterating a huge array or connecting with external services like database or network-based API.
The Weakest Link In The Chain
I think it’s a common sense, that software users require it to run instantly. Every response to interaction is expected to be immediate, because what users hate most, is wasting their time to stare at the loading indicator. If reactions take less than one second then good for you, people will love it. But don’t get too comfortable.
As your userbase and features number starts to grow, so the usage of the underlying resources. Every component of your system or program, has some maximum capacity beyond which it cannot operate efficiently. If in the early phase of your project it was taking 0.5 second to list your bank account operations, and now it takes 3 seconds or more, it’s obvious that one or more components have reached their maximum throughput. Your users will definitely not like it, they will soon become angry and frustrated because of the time they have wasted to use your clumsy software.
Just like your trip is a sum of all interactions, so is your Synchronous Program. Let’s take a closer look at what happens on a high level when you fetch a list of operations in your banking software. First, there is a network connection being established to the bank’s API. Since it’s a network, we cannot be sure how it will behave. There are so many things that can go wrong in this process, it’s impossible to write them all down. Congestion, distance and current server load are three of the broadest aspects we can think of, and any of them may be a reason for the connection to be slow. And this is only the first step of obtaining required data. The next one is time to process our request by the server we are connected to. Finally, server sends us a requested data, so we can present it to the user. The longer it takes for every subprocess to complete, the longer total time of waiting for the list to show up.
Asynchronous Model
As you remember, getting yourself to the beach wasn’t the only plan for that day. The ice-cream and waffles must be consumed, so everyone can call it a day with a sense of satisfaction. This is a situation which doesn’t require you to execute tasks in some particular order, to end up successfully. You can have your desserts right before you wake up, on your way home or even make them after you come back home. It doesn’t really mater, what does matter is to have them all.
Asynchronous Programming Means Concurrent
In Asynchronous Model, instructions also run one after another, but this time the execution doesn’t wait for the current line of code to complete, it goes to the next row and schedules it for execution. The most important aspect of this model, is that it doesn’t block a thread or process which it runs under. After reaching the last line of code, program is ready to make another set of procedures, even if results of the queued operations are still being processed.
This approach, unlike Synchronous, lets you start some set of tasks concurrently, meaning at the same time. If it doesn’t matter which one ends first or last - good for you. You can just fire and forget them, they will eventually finish doing what they were programmed to.
It Is More Like A Race
When we reason about sequential processing, we keep in mind a chain-like nature of this. The whole chain of operations is run consecutively, and time needed for every link to complete decides how long the chain will be executing. In Asynchronous realm, it’s quite different though. When tasks are run concurrently, total time needed for them to finish is equal to execution period of the longest one. Let me use a banking application example once again.
This time we are not displaying a list of operations on your account, but we will obtain a list of your credit cards. First step which needs to be done, is to get a list of cards. Second - send another request for every card, which will provide you necessary details. After scheduling a first request, we have to wait for it to complete, because it contains numbers identifying resources we need to fetch next. It doesn’t only let you present a card which details are already loaded. The main thread is free, so the program is ready to receive and handle your another interaction. The whole process will take as much time as it was needed for the longest request to finish. The typical race works just the same, it starts and finishes when the last player will complete it.
Which Model Is Better?
Will it be much of a surprise, if I tell you: “it depends”? If you read my articles frequently, you have probably already noticed, that this is an answer for every question asked this way. In most cases there is no division to a better or worse solution, but what problems does it solve or what is it suitable for. There are use cases for sync, and there are usages for async programming. What are they exactly?
Sync
This model will be a great choice when you search for simplicity. Remembering, that source code is for humans, it is easier to get a grasp on the synchronous code. That’s because it is executed exactly how it is written, the order of the operations is always the same, so reader can focus on the logic rather than having another clues on when given function will run. This model is implemented by the most of the programming languages out there, like Java, Python, C, PHP or Ruby. Since the business logic is sometimes hard by itself, it is a common practice to use sync languages to operate on backend systems accessed mostly via HTTP protocol or background processes where time of completion is not an issue.
Async
The great use case for async programming languages are programs providing a User Interface. Applications having an interactive components like buttons or notifications must be responsive no matter what. If there is a button which, when clicked, is making an API call to some HTTP service, it cannot block the entire thread until it completes. Instead, it schedules this operation to some special kind of thread, when the main one is still responsive and lets you interact with the remaining components. Just like sync languages, it can, and should be applied when it comes to backend systems, but it requires to have a qualified and experienced team of programmers to handle an increased complexity. The most popular example of async language, in both Node and web browsers, is definitely a Javascript. It’s not fully asynchronous, but all I/O operations like network or filesystem will be executed in a separate context. Thanks to this, a main thread will not be blocked, thus ready to accept another tasks.