What's the Difference Between Blocking vs Non-Blocking and Sync vs Async?

189

Overview

During application development, we often confront terms like "Blocking," "Non-blocking," "Synchronous," and "Asynchronous." It's a common misconception to view these as synonymous. In reality, they represent distinct, albeit intertwined, concepts.

One frequent point of confusion is between "Blocking" and "Synchronous" and between "Non-blocking" and "Asynchronous."

In essence:

  • "Synchronous" and "Asynchronous" processes involve at least two subjects. When their task durations align, it's "Synchronous"; if not, it's "Asynchronous."

  • "Blocking" and "Non-blocking" describe how systems handle tasks. These terminologies are primarily applied to I/O operations.

Let's delve into these concepts in more detail.

Concept

1. Understanding Blocking vs. Non-Blocking

The crux of this distinction lies in how a system responds when waiting for another process.

  • Blocking: The system waits for another process to complete before resuming its task. Think of a Java JDBC querying a database—it waits for a response.

  • Non-Blocking: The system continues its operation, irrespective of other processes. It doesn't wait.

2. Deciphering Synchronous vs. Asynchronous

When considering multiple subjects executing tasks:

  • Synchronous: The tasks align, either starting or ending simultaneously.

  • Asynchronous: Task start/end times are independent of each other.

3. Blocking/Non-blocking vs. Sync/Async

When an I/O function is invoked:

  • Blocking: Waits for the process to complete before returning.

  • Non-Blocking: Returns immediately, regardless of whether the task has finished.

In terms of who oversees task completion for the I/O function:

  • Synchronous: Managed by the application.

  • Asynchronous: Overseen by the kernel.

Based on the combinations of Blocking/Non-Blocking and Synchronous/Asynchronous, the four resulting quadrants are shown in the diagram below:

Study Notes - I/O Models | Complete Think

Examples of Their Combinations

  1. Synchronous Blocking I/O

This is the most straightforward method of I/O. When you make a call, the program waits for the operation to complete before moving on.

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("example.txt");
    std::string content;

    // This line blocks until the entire file is read
    std::getline(file, content, '\0');

    std::cout << content;

    file.close();
    return 0;
}
  1. Synchronous Non-blocking I/O

In this mode, the system would regularly check (or poll) if data is available. If not, it would continue doing something else.

// This is a simplified example. True non-blocking I/O in C++ can be complex.
// Assuming a non-blocking I/O library function 'try_read'

/*
bool try_read(std::ifstream& file, std::string& content);
*/

int main() {
    std::ifstream file("example.txt");
    std::string content;

    while (!try_read(file, content)) {
        // Do other tasks or sleep for a while
    }

    std::cout << content;

    file.close();
    return 0;
}
  1. Asynchronous Blocking I/O

This sounds contradictory, but it can be thought of as starting an I/O operation asynchronously, but once started, that particular I/O operation would block until completion. This is often used with I/O multiplexing.

// An example using pseudo-code, as actual implementations vary.
// Assuming a function 'async_read' that starts reading and blocks until it completes.

/*
void async_read(std::ifstream& file, void (*callback)(std::string content));
*/

void print_content(std::string content) {
    std::cout << content;
}

int main() {
    std::ifstream file("example.txt");

    async_read(file, print_content);

    // Do other tasks while I/O is ongoing

    file.close();
    return 0;
}
  1. Asynchronous Non-Blocking I/O (AIO):

In this mode, the I/O operation starts and the control returns immediately, allowing the program to continue executing subsequent statements without waiting.

// Assuming a library function 'async_read_nonblocking'
/*
void async_read_nonblocking(std::ifstream& file, void (*callback)(std::string content));
*/

void print_content(std::string content) {
    std::cout << content;
}

int main() {
    std::ifstream file("example.txt");

    async_read_nonblocking(file, print_content);

    // Do other tasks immediately without waiting for the I/O to complete

    // Wait or poll for the asynchronous I/O to complete if needed

    file.close();
    return 0;
}

Conclusion

These concepts revolve around how applications and kernels interact. Distinguishing between them requires understanding the difference between the application and kernel domains. Reviewing the explanations, associated diagrams, and code examples above can provide a clearer picture.

References