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:
Let's delve into these concepts in more detail.
The crux of this distinction lies in how a system responds when waiting for another process.
When considering multiple subjects executing tasks:
When an I/O function is invoked:
In terms of who oversees task completion for the I/O function:
Based on the combinations of Blocking/Non-Blocking and Synchronous/Asynchronous, the four resulting quadrants are shown in the diagram below:
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;
}
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;
}
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;
}
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;
}
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.