In programming, discussions about synchronization typically involve at least two interacting objects.
Sync means, in that context, two objects are tightly intertwined that one task(A) is dependent on the other(B).

If A can execute its own task without a constraint, A and B is async.
When function A invokes function B, if the operating system pauses the thread or process running A during the call to B, this indicates a blocking operation. Otherwise, the operation is non-blocking.

Likewise, blocking call means to pause thread/process - that doesn't mean that every function call pauses thread execution as follows:
fn some_work(a:i32,b:i32) -> i32{
a+b
}
fn main(){
let c = some_work(1,1);
}
The thread running main doesn't get blocked just because it invokes some_work.
But then, in what situation a thread/process get blocked by operating system?
Most blocking is related to I/O because CPU processing is much faster than that of other devices, such as disks, keyboards, and networks. Therefore, when a program, thread, or process encounters an I/O task, it yields CPU time back to the operating system, which then assigns it to a different thread, as follows:

From the CPU's perspective, it never wastes time; it continues working on a different thread, polling jobs from the waiting queue. However, as programmers, we care about how quickly a program handles its tasks. In this case, frequent blocking can slow things down. Is there a way to handle I/O tasks without pausing the program? Yes, with non-blocking I/O calls.
Take network data transfer as an example. If the data receiving function recv is non-blocking, it means that instead of yielding control, the OS returns from recv immediately. The invoking thread continues its work, while data reception is handled by the kernel:

But how can your program know when the data has been received? Three mechanisms are commonly suggested:
recv supports callbacks).recv call.Let's take a look at Rust example for non-blocking asynchoronous call:
use tokio::{net::TcpListener, io::AsyncReadExt};
async fn handler(data: &[u8]) {
// process network data
// ...
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
// Spawn a new task for each connection
tokio::spawn(async move {
let mut buf = vec![0; 1024];
// Non-blocking read using async/await
match socket.read(&mut buf).await {
Ok(n) if n > 0 => {
handler(&buf[..n]).await;
}
_ => {
// Handle error or connection closed
}
}
});
}
}