Synchronous Vs Asynchronous Programming

Key differences between the programming models

Animesh Gaitonde
CodeX
8 min readAug 17, 2021

--

Asynchronous Vs Synchronous

Introduction

Single processor systems dominated during the early days of computing. The synchronous programming model was adopted in many early programming languages. With the advancements in hardware, there was a need to develop better programming models. A robust model that would fully utilize all the resources would be the most suitable one. This gave birth to Asynchronous programming.

For beginners, it’s difficult to understand the difference between the two programming models. In this write-up, I’ll attempt to simplify & explain the difference in layman terms. We will also take a look at a simple illustration in Java. We will understand APIs provided in Java for asynchronous programming.

Real-World Analogy

Before diving deep into programming models, we will first take a look at synchronous & asynchronous systems.

Supermarket as a Synchronous system

We all have been in supermarket stores to buy our daily items. Every person collects items in a trolley and goes to the counter. The guy at the counter will scan all the items, prepare a bill, accept payment from the person and finally give the receipt. For every customer, this part is a time taking process. Further, the time taken is in proportion to the number of items, any technical glitches, and time to find a change (cash payments).

Imagine what would happen if it was a holiday season and a sale in the supermarket. Customers would have to spend a lot of their time in the queue, thereby aggravating their frustration. Following would be an apt reaction of every customer:-

In the above example, you can think of the Cashier as a CPU & the Customer as a programming function. Similar to programming functions, every customer takes a different time and has a distinct requirement. A Cashier processes a customer’s request and spends different times depending on the customer’s purchase, & payment mode.

Each Customer is blocked until the Cashier releases their receipt. They can’t do anything else and have to wait in the queue. It would have been great if Customers could just hand their order to Cashier, visit some other place, and return to the supermarket again. This would mean less frustration for the customers.

In a synchronous system, we wait for a task to finish & then process the next task. As we saw it leads to bottlenecks and reduces the throughput of the system.

McDonald’s as an Asynchronous system

McDonald’s has self-service kiosk terminals. Customers can place their orders and complete their payments. After the payment is done, a receipt is dispensed along with the order number. Once the order is ready, the order number is displayed on the screen.

While the food is being prepared, customers can do multiple things in parallel. They aren’t blocked on the person preparing the order and the store can thus avoid crowding. Customers won’t receive their orders in sequential order. For eg:- A person may receive his food before another person even though he would have placed an order after him. This is an example of the asynchronous system.

An asynchronous system doesn’t wait for a task to finish. It moves on to another task while a given task is in progress. Let’s have a look at a programming example.

Synchronous Programming

In synchronous programming, each line of code is executed sequentially. The executing thread blocks in case of a network or a file IO call. Let’s see an illustration of synchronous code execution.

We will take an example of an eCommerce system. Let’s say we have a function that fetches all the items given an order. It then creates a CSV file for the items and stores it. Further, it sends the order status information to the client via email. Our code will be executed sequentially in the above case, i.e. lines 10, 11, and then 12.

The fetchItems functions perform a database call. Database call is expensive as it’s an IO call. As soon as we call the fetchItems method, the main thread will block. It will resume once all the items are returned from the database successfully.

The createCsvFileForItems the function processes the items and generates a CSV file. This function again will put the main thread in a waiting state. Once the file is created successfully, the function will return. Further, it will invoke the sendOrderStatus method.

The methodsendOrderStatususes an email client. It uses the email address and sends the order status to the customer.

Assume that the time taken by each function is as follows:-

  • fetchItems - 5 milliseconds
  • createCsvFileForItems - 10 milliseconds
  • sendOrderStatus - 8 milliseconds

The overall time taken would be 23 milliseconds for the program to complete. The sendOrderStatus function will be called after 15 milliseconds. This function is independent of fetching items and processing them. However, it still has to wait for the previous two functions to complete. We could have called the sendOrderStatus first. But in this case, the createCsvFileForItems function would have to wait for 8 milliseconds.

Time taken for each function

Additionally, the main thread blocks when a function gets called. As the thread goes into a wait state, it’s unable to do any useful work. If the application is running on a multi-core CPU, then only one CPU is getting utilized. This would lead to other CPUs lying idle.

We could fully utilize all the CPU cores, by executing independent tasks in different threads. In the above example, this could be accomplished by running fetchItems and sendOrderStatus in parallel. On completion, we could use the result & then call createCsvFileForItems.

Asynchronous Programming

In Asynchronous programming, independent tasks are executed in parallel on different threads. Once a task completes and returns a result, a dependent task gets invoked as a callback. Independent tasks don’t block the main thread and we can utilize all the cores of the CPU.

In the above code, we have an asynchronous version of each method. The code doesn’t execute lines 32, 33, and 34 in sequence. It will call the fetchItemsAsync method and offload it to one of the threads. Subsequently, it will call the sendOrderStatusAsync method without waiting for the first function to finish. This method will run on a separate thread on a different CPU. On line 33, the fetchItemsAsync method returns a future. The main thread will wait on this future on line 35 and get its value.

The execution of the function will be as shown in the below diagram:-

Asynchronous function execution

Asynchronous Programming in Java

Java provides the CompletionStage interface and CompletableFuture class to implement asynchronous programming. A Future or a CompletableFuture represents the result of a future computation. For eg:- You can call an async method that returns a future. After calling this method, you can continue the execution of your code. We use the API to get the result of computation from the future. Following is a simple example:-

In the above example, the method fetchItemsAsync returns a future containing a list of items. This method defines and initializes a CompletableFuture. With the help of Executor API, it offloads the execution to a new thread. Once the computation is done, it completes the future (line 48) and then returns it.

Since the fetchItemsAsync is asynchronous, the main thread can simultaneously call sendOrderStatus method. Thus, it doesn’t need to wait for the former method to finish. It will then use the get method in the future to get the final result. The main thread would block on calling the get method, and resume once the result is available.

We can avoid the above boilerplate code & make use of CompletableFuture’s APIs. CompletableFuture provides the methods supplyAsync and runAsync. The former takes a supplier interface whereas the latter uses a runnable. The Supplier interface is a functional interface that doesn’t take any arguments. It returns a value of a parameterized type.

Using Completable Futures

We usesupplyAsync function in the above code snippet. Internally, this function will execute the code in one thread from the ForkJoinPool in java. The thenApplyconstruct processes the result of an asynchronous computation stage.

As shown above, the fetchItems call returns a list of items asynchronously. The result is then fed to the createCsvFileForItems function. This function acts as a callback function and gets invoked once the fetchItems function completes. The program starts executing the function sendOrderStatus , without waiting for the first stage of computation to complete. Thus, we have decoupled two independent tasks using asynchronous programming.

Pros & Cons of Synchronous Programming

Pros

  • Synchronous programming is straightforward & easy to implement.
  • It’s ideal for CPU-bound tasks.
  • More suitable for simple systems.
  • Debugging a synchronous system is easy.

Cons

  • It’s not suitable for IO-bound tasks. CPU cores are underutilized.
  • It introduces a dependency between two tasks. A task can block the execution of another task.
  • Not suitable for high throughput and low-latency systems

Pros & Cons of Asynchronous Programming

Pros

  • Independent tasks can be launched in parallel. It fully utilizes all the computing resources.
  • Favorable for IO-bound tasks.
  • Scalable and suitable for high throughput workloads.

Cons

  • Requires a lot of callback functions. Debugging becomes difficult sometimes.
  • Not favorable for CPU-intensive tasks.
  • Difficult to implement in some programming languages.

References

--

--

Animesh Gaitonde
CodeX
Writer for

SDE-3/Tech Lead @ Amazon| ex-Airbnb | ex-Microsoft. Writes about Distributed Systems, Programming Languages & Tech Interviews