😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号: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::async
和std::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;
}
在这个示例中,connect
、send
和recv
函数都是阻塞调用。如果它们不能立即完成,比如服务器没有立即响应,调用线程将会被阻塞,直到操作完成。
非阻塞(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
函数将套接字设置为非阻塞模式。当connect
、send
或recv
调用不能立即完成时,它们会立即返回而不是阻塞调用线程。
组合模式
同步阻塞(Synchronous Blocking)
同步阻塞模式下,调用方在发起请求后,必须等待结果返回,且在等待期间不能执行其他任务。
例子:
- 去照相馆拍照,拍完照片后,商家说需要30分钟才能洗出来照片。如果顾客在店里等待照片洗好,这个过程就是同步阻塞。
同步非阻塞(Synchronous Non-blocking)
同步非阻塞模式下,调用方在发起请求后,可以立即返回,但需要周期性地检查结果是否可用。
例子:
- 顾客在照相馆拍照后,继续在店里刷手机,时不时地询问老板照片是否洗好,这个过程就是同步非阻塞。
异步阻塞(Asynchronous Blocking)
理论上,异步阻塞模式是不存在的。因为异步操作本身就是为了不阻塞调用方,如果还要等待结果,就失去了异步的意义。
异步非阻塞(Asynchronous Non-blocking)
异步非阻塞模式下,调用方在发起请求后,可以继续执行其他任务,当请求处理完成时,通过回调或其他方式通知调用方。
例子:
- 顾客在照相馆拍照后,留下手机号,然后去公园等待老板的电话通知照片洗好,这个过程就是异步非阻塞。
图解
以下是对这些模式的图解说明:
- 同步阻塞:
- Sender 发送请求后,等待 Receiver 返回结果,期间 Sender 被阻塞,不能处理其他任务。
-
同步非阻塞:
- Sender 发送请求后,立即返回,然后不断轮询 Receiver,直到收到结果。Sender 在等待期间可以处理其他任务。
-
异步非阻塞:
- Sender 发送请求后,立即返回,等待 Receiver 的回调。Sender 在等待期间可以处理其他任务。
图借鉴于csdn
祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~
🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经。
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~