同步与异步,阻塞与非阻塞的深入分析

😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的微信交流:sssun902
🎈 本文专栏:本文收录于《动手学操作系统》系列专栏,相信一份耕耘一份收获,我会分享OS相关学习内容,不说废话,祝大家都offer拿到手软
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥随时欢迎您跟我沟通,一起交流,一起成长、进步!

同步与异步,阻塞与非阻塞的深入分析

在计算机科学中,同步与异步、阻塞与非阻塞是描述程序行为的常用术语。理解这些概念对于编写高效、可靠的程序至关重要。本文将详细分析这些概念,并结合具体例子进行说明。
在这里插入图片描述

同步与异步

在这里插入图片描述

同步(Synchronous)

同步操作指的是请求发起方在发起请求后,必须等待结果返回才能继续执行其他任务。这种模式下,程序的执行是顺序的,一个任务完成后才能开始下一个任务。

特点:

  • 请求发起后,调用方必须等待结果。
  • 调用方在等待期间不能执行其他任务。

例子:

  • 普通B/S模式(同步):提交请求 -> 等待服务器处理 -> 处理完毕返回。在这个过程中,客户端浏览器不能执行其他任务。

异步(Asynchronous)

异步操作则是指请求发起后,调用方不需要立即等待结果,可以继续执行其他任务。当请求处理完成时,通过状态改变、消息通知或回调函数等方式通知调用方。

特点:

  • 请求发起后,调用方不需要立即等待结果。
  • 调用方可以在等待期间执行其他任务。

例子:

  • AJAX请求(异步):请求通过事件触发 -> 服务器处理(浏览器仍然可以执行其他任务) -> 处理完毕。

同步异步代码示例

在C++中,同步和异步的实现可以通过多种方式完成,例如使用标准库中的线程、互斥锁、条件变量等。以下是一些示例:

同步(Synchronous)代码示例

以下是一个简单的同步文件读取示例,使用C++标准库中的文件流进行同步IO操作:

#include <iostream>
#include <fstream>
#include <string>

std::string readFileSync(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) {
        throw std::runtime_error("Unable to open file");
    }

    std::string content((std::istreambuf_iterator<char>(file)),
                         (std::istreambuf_iterator<char>()));
    return content;
}

int main() {
    try {
        std::string content = readFileSync("example.txt");
        std::cout << content << std::endl;
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

在这个示例中,readFileSync函数打开文件并同步读取其内容。这将阻塞,直到文件内容被完全读取。

异步(Asynchronous)代码示例

C++11引入了std::asyncstd::future,可以用来实现异步操作。以下是一个使用std::async进行异步文件读取的示例:

#include <iostream>
#include <fstream>
#include <string>
#include <future>

std::string readFileAsync(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) {
        throw std::runtime_error("Unable to open file");
    }

    std::string content((std::istreambuf_iterator<char>(file)),
                         (std::istreambuf_iterator<char>()));
    return content;
}

int main() {
    auto future = std::async(std::launch::async, readFileAsync, "example.txt");

    try {
        std::string content = future.get();  // 等待异步操作完成
        std::cout << content << std::endl;
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

在这个示例中,std::async启动了一个异步任务来读取文件。future.get()会阻塞,直到异步操作完成并返回结果。

使用线程的异步示例

以下是一个更复杂的异步示例,使用C++11的线程库:

#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::string content;
std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void readFileAsync(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) {
        throw std::runtime_error("Unable to open file");
    }

    std::string local_content((std::istreambuf_iterator<char>(file)),
                               (std::istreambuf_iterator<char>()));
    std::lock_guard<std::mutex> lock(mtx);
    content = std::move(local_content);
    ready = true;
    cv.notify_one();
}

int main() {
    std::thread t(readFileAsync, "example.txt");

    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });

    t.join();  // 确保线程完成执行

    try {
        std::cout << content << std::endl;
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

在这个示例中,一个线程被用来异步读取文件。主线程通过std::condition_variable等待文件读取完成,然后输出内容。

示例展示了如何在C++中实现同步和异步操作。同步操作直接在主线程中完成,而异步操作则通过多线程或其他并发机制来实现。

阻塞与非阻塞

阻塞(Blocking)

阻塞调用是指在调用结果返回之前,当前线程会被挂起,直到结果返回。在阻塞期间,线程不能执行其他任务。

特点:

  • 调用结果返回之前,线程被挂起。
  • 线程在等待期间不能执行其他任务。

例子:

  • 在socket编程中,如果缓冲区中没有数据,调用recv函数会一直等待,直到有数据才返回。此时,当前线程会被挂起。

非阻塞(Non-blocking)

非阻塞调用则是指在不能立即得到结果之前,该函数不会阻塞当前线程,而会立即返回。调用方需要周期性地检查结果是否可用。

特点:

  • 调用结果未返回时,线程不会被挂起。
  • 线程在等待期间可以执行其他任务。

例子:

  • 如果将socket设置为非阻塞模式,调用recv函数时,如果缓冲区中没有数据,则系统调用立即返回,不会挂起线程。线程会继续轮询,直到socket内核缓冲区内有数据为止。

阻塞和非阻塞代码示例

在C++中,阻塞和非阻塞的概念通常与I/O操作相关,特别是网络编程和文件I/O。以下是使用C++进行套接字编程的阻塞和非阻塞示例。

阻塞(Blocking)I/O 示例

以下是一个使用阻塞I/O的C++网络客户端示例:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        std::cerr << "Socket creation failed" << std::endl;
        return 1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080); // 假设服务器监听在8080端口
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器地址

    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "Connection failed" << std::endl;
        return 1;
    }

    const char *message = "Hello, Server!";
    if (send(sock, message, strlen(message), 0) < 0) {
        std::cerr << "Send failed" << std::endl;
        return 1;
    }

    char buffer[1024] = {0};
    if (recv(sock, buffer, sizeof(buffer), 0) < 0) {
        std::cerr << "Receive failed" << std::endl;
        return 1;
    }

    std::cout << "Server response: " << buffer << std::endl;
    close(sock);
    return 0;
}

在这个示例中,connectsendrecv函数都是阻塞调用。如果它们不能立即完成,比如服务器没有立即响应,调用线程将会被阻塞,直到操作完成。

非阻塞(Non-blocking)I/O 示例

要设置套接字为非阻塞模式,可以使用fcntl函数:

#include <fcntl.h>

以下是使用非阻塞I/O的C++网络客户端示例:

// ...(其他包含和定义与上面相同)

int setNonBlocking(int sock) {
    int opts = fcntl(sock, F_GETFL);
    if (opts < 0) {
        std::cerr << "fcntl failed" << std::endl;
        return -1;
    }
    opts = opts | O_NONBLOCK;
    if (fcntl(sock, F_SETFL, opts) < 0) {
        std::cerr << "fcntl failed" << std::endl;
        return -1;
    }
    return 0;
}

int main() {
    // ...(创建套接字的代码与上面相同)

    if (setNonBlocking(sock) < 0) {
        return 1;
    }

    // ...(设置服务器地址的代码与上面相同)

    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        if (errno != EINPROGRESS) {
            std::cerr << "Connection failed" << std::endl;
            return 1;
        }
        // 连接正在进行中,可以继续其他任务
    }

    // ...(发送消息的代码与上面相同)

    char buffer[1024] = {0};
    while (recv(sock, buffer, sizeof(buffer), 0) < 0) {
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            std::cerr << "Receive failed" << std::endl;
            break;
        }
        // 没有数据可读,可以继续其他任务或轮询
    }

    // ...(打印服务器响应和关闭套接字的代码与上面相同)
}

在这个示例中,通过setNonBlocking函数将套接字设置为非阻塞模式。当connectsendrecv调用不能立即完成时,它们会立即返回而不是阻塞调用线程。

组合模式

同步阻塞(Synchronous Blocking)

同步阻塞模式下,调用方在发起请求后,必须等待结果返回,且在等待期间不能执行其他任务。

例子:

  • 去照相馆拍照,拍完照片后,商家说需要30分钟才能洗出来照片。如果顾客在店里等待照片洗好,这个过程就是同步阻塞。

同步非阻塞(Synchronous Non-blocking)

同步非阻塞模式下,调用方在发起请求后,可以立即返回,但需要周期性地检查结果是否可用。

例子:

  • 顾客在照相馆拍照后,继续在店里刷手机,时不时地询问老板照片是否洗好,这个过程就是同步非阻塞。

异步阻塞(Asynchronous Blocking)

理论上,异步阻塞模式是不存在的。因为异步操作本身就是为了不阻塞调用方,如果还要等待结果,就失去了异步的意义。

异步非阻塞(Asynchronous Non-blocking)

异步非阻塞模式下,调用方在发起请求后,可以继续执行其他任务,当请求处理完成时,通过回调或其他方式通知调用方。

例子:

  • 顾客在照相馆拍照后,留下手机号,然后去公园等待老板的电话通知照片洗好,这个过程就是异步非阻塞。

图解

以下是对这些模式的图解说明:

  1. 同步阻塞
    • Sender 发送请求后,等待 Receiver 返回结果,期间 Sender 被阻塞,不能处理其他任务。

在这里插入图片描述

  1. 同步非阻塞

    • Sender 发送请求后,立即返回,然后不断轮询 Receiver,直到收到结果。Sender 在等待期间可以处理其他任务。
  2. 异步非阻塞

    • Sender 发送请求后,立即返回,等待 Receiver 的回调。Sender 在等待期间可以处理其他任务。

在这里插入图片描述图借鉴于csdn

祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~


🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~


  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员行者孙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值