介绍
同步
概念
在计算机领域,同步是指在执行某个操作时,程序会等待该操作完成后才继续执行后续的代码。也就是说,操作是按照顺序依次进行的,一个操作完成之后才会开始下一个操作。在同步操作过程中,程序处于阻塞状态,无法进行其他任务,直到当前操作结束。
应用场景
- 数据一致性要求高:在一些对数据一致性要求非常严格的场景中,同步操作是必要的。例如,在银行转账系统中,必须确保一笔转账操作完全完成后,才能进行下一笔操作,以保证账户余额的准确性和数据的一致性。
- 简单的顺序任务:对于一些简单的、不需要并发处理的顺序任务,同步操作可以使代码逻辑更加清晰易懂。比如,一个程序需要依次读取文件的不同部分并进行处理,使用同步操作可以按顺序完成这些任务。
优缺点
- 优点
- 逻辑简单:同步操作的代码逻辑清晰,易于理解和调试。因为操作是按顺序执行的,程序员可以很容易地预测程序的执行流程。
- 数据一致性好:由于操作是顺序执行的,不会出现多个操作同时修改数据导致的数据不一致问题。
- 缺点
- 性能瓶颈:在进行 I/O 密集型任务时,同步操作会导致程序长时间阻塞,浪费 CPU 资源。例如,当程序发起一个磁盘读写请求时,CPU 会等待该请求完成,在此期间无法执行其他任务。
- 并发处理能力差:同步操作难以处理大量的并发请求。如果有多个请求同时到达,只能依次处理,会导致响应时间变长,系统吞吐量降低。
异步
概念
异步是指在执行某个操作时,程序不会等待该操作完成,而是继续执行后续的代码。当操作完成后,会通过某种机制(如回调函数、事件通知等)通知程序。在异步操作过程中,程序不会被阻塞,可以继续处理其他任务,从而提高了系统的并发性能和资源利用率。
应用场景
- I/O 密集型任务:对于需要大量等待外部资源的 I/O 密集型任务,异步操作可以显著提高性能。例如,在网络编程中,当客户端向服务器发送请求后,使用异步操作可以在等待服务器响应的同时处理其他任务,而不需要一直阻塞。
- 高并发场景:在需要处理大量并发请求的场景中,异步操作可以更好地应对。例如,一个 Web 服务器需要同时处理多个客户端的请求,如果使用异步操作,服务器可以同时处理多个请求,提高响应速度和系统吞吐量。
优缺点
- 优点
- 提高性能:异步操作可以充分利用 CPU 资源,避免了在 I/O 操作时的阻塞等待,从而提高了系统的整体性能。
- 高并发处理能力:异步操作可以同时处理多个任务,能够更好地应对高并发场景,提高系统的响应速度和吞吐量。
- 缺点
- 编程复杂度高:异步操作的代码逻辑相对复杂,需要使用回调函数、事件驱动等机制来处理操作结果,增加了编程的难度和代码的复杂度。
- 调试困难:由于异步操作的执行顺序不是固定的,调试时很难跟踪程序的执行流程,增加了调试的难度。
- 额外开销:异步操作会引入一些额外的开销,如数据结构和上下文管理开销、回调函数管理开销、错误处理和状态管理开销等。
对比
同步操作速度优势情况
- 操作简单且数据量小:当操作非常简单,涉及的数据量较小,并且不需要等待外部资源(如磁盘 I/O、网络响应)时,同步操作可能更快。因为同步操作的流程较为直接,没有额外的异步调度开销。例如,在一个简单的程序中对本地内存中的小数据块进行读写操作,同步操作可以立即完成,无需进行复杂的异步任务管理。
- 资源竞争少:在资源竞争较少的环境中,同步操作可以按照顺序依次执行,不会出现因为异步操作的并发执行而导致的资源冲突和协调开销。比如在一个单线程且资源充足的系统中,同步操作可以高效地利用资源,快速完成任务。
异步操作速度优势情况
- I/O 密集型任务:对于需要大量等待外部资源的 I/O 密集型任务,异步操作通常更快。在同步 I/O 中,当发起一个 I/O 请求时,程序会被阻塞,直到该请求完成,这段时间内 CPU 处于空闲状态。而在异步 I/O 中,程序在发起 I/O 请求后可以继续执行其他任务,当 I/O 操作完成时会收到通知。例如,在进行大量的磁盘读写或网络数据传输时,异步操作可以充分利用 CPU 时间,提高整体效率。
- 高并发场景:在高并发场景下,异步操作能够更好地处理多个并发请求。每个异步任务可以独立执行,不会相互阻塞,从而提高系统的吞吐量。例如,一个 Web 服务器需要同时处理大量的客户端请求,如果使用同步操作,服务器可能会因为某个请求的处理时间过长而阻塞其他请求;而使用异步操作,服务器可以同时处理多个请求,提高响应速度。
异步调度相比同步额外开销
1. 数据结构和上下文管理开销
- 异步控制块(AIOCB) :在使用异步 I/O 时,通常需要为每个异步操作创建一个异步控制块(如 POSIX AIO 中的
struct aiocb)。这个结构体用于存储异步操作的相关信息,如文件描述符、缓冲区指针、偏移量等。每个异步操作都需要一个这样的结构体,当有大量异步操作时,会占用一定的内存空间。而且,操作系统需要对这些结构体进行管理和维护,增加了系统的内存管理开销。 - 上下文切换:异步操作通常涉及多线程或多进程的使用。当异步操作完成时,可能需要进行上下文切换来执行回调函数。上下文切换需要保存和恢复寄存器、程序计数器等信息,这会消耗 CPU 时间,尤其是在频繁进行异步操作时,上下文切换的开销会更加明显。
2. 回调函数管理开销
- 回调函数注册和调用:异步操作通常依赖回调函数来处理操作结果。在发起异步操作时,需要将回调函数注册到系统中。当操作完成时,系统会调用相应的回调函数。这个过程涉及到函数指针的管理和调用,需要一定的开销。而且,如果回调函数比较复杂,执行回调函数本身也会消耗 CPU 时间。
- 回调函数的并发控制:在多线程环境下,多个异步操作可能同时完成并触发回调函数。为了保证回调函数的正确执行,可能需要进行并发控制,如使用锁机制。锁的使用会引入额外的开销,并且可能导致线程阻塞,降低系统性能。
3. 错误处理和状态管理开销
- 异步操作状态跟踪:由于异步操作是非阻塞的,程序需要不断跟踪异步操作的状态。通常使用函数(如 POSIX AIO 中的
aio_error)来检查操作是否完成或是否出错。这个过程需要频繁地访问系统状态,增加了系统调用的开销。 - 错误处理复杂性:异步操作的错误处理比同步操作更加复杂。在同步操作中,操作完成后可以立即检查返回值来判断是否出错;而在异步操作中,需要在回调函数中进行错误处理,并且可能需要处理多个异步操作的错误情况。错误处理的复杂性增加了代码的复杂度和执行开销。
4. 线程或进程创建和管理开销
- 线程或进程创建:在实现异步操作时,可能需要创建额外的线程或进程来处理异步任务。线程或进程的创建需要分配内存、初始化堆栈等,这会消耗一定的系统资源。而且,线程或进程的创建和销毁操作本身也会带来开销。
- 线程或进程调度:多个线程或进程之间需要进行调度,以确保它们能够合理地使用 CPU 资源。操作系统的调度算法需要考虑线程或进程的优先级、状态等因素,这会增加系统的调度开销。
实例
这是一个在Linux系统下nve读写操作的实验,我们来看一下异步和同步程序的区别在哪
同步
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#define BUFFER_SIZE 1024 * 1024 * 1000 // 1000MB 缓冲区大小
#define FILENAME "sync_test_file"
int main() {
int fd;
char *buffer;
ssize_t bytes_written, bytes_read;
clock_t start_time, end_time;
double elapsed_time;
double write_speed, read_speed;
// 分配缓冲区
buffer = (char *)malloc(BUFFER_SIZE);
if (buffer == NULL) {
perror("内存分配失败");
return 1;
}
memset(buffer, 'A', BUFFER_SIZE);
// 打开文件用于写入
fd = open(FILENAME, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd == -1) {
perror("打开文件用于写入失败");
free(buffer);
return 1;
}
// 记录写操作开始时间
start_time = clock();
// 进行写操作
bytes_written = write(fd, buffer, BUFFER_SIZE);
if (bytes_written == -1) {
perror("写操作失败");
close(fd);
free(buffer);
return 1;
}
// 记录写操作结束时间
end_time = clock();
// 计算写操作用时
elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
// 计算写速度(MB/s)
write_speed = (double)bytes_written / elapsed_time / (1024 * 1024);
// 输出写操作信息
printf("同步写操作完成,传输了 %zd 字节,用时 %.6f 秒,速度 %.2f MB/s\n", bytes_written, elapsed_time, write_speed);
// 关闭文件
close(fd);
// 打开文件用于读取
fd = open(FILENAME, O_RDONLY);
if (fd == -1) {
perror("打开文件用于读取失败");
free(buffer);
return 1;
}
// 记录读操作开始时间
start_time = clock();
// 进行读操作
bytes_read = read(fd, buffer, BUFFER_SIZE);
if (bytes_read == -1) {
perror("读操作失败");
close(fd);
free(buffer);
return 1;
}
// 记录读操作结束时间
end_time = clock();
// 计算读操作用时
elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
// 计算读速度(MB/s)
read_speed = (double)bytes_read / elapsed_time / (1024 * 1024);
// 输出读操作信息
printf("同步读操作完成,传输了 %zd 字节,用时 %.6f 秒,速度 %.2f MB/s\n", bytes_read, elapsed_time, read_speed);
// 关闭文件
close(fd);
// 释放缓冲区
free(buffer);
return 0;
}
异步
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#define BUFFER_SIZE 1024 * 1024 * 1000 // 1000MB 缓冲区大小
#define FILENAME "sync_test_file"
int main() {
int fd;
char *buffer;
ssize_t bytes_written, bytes_read;
clock_t start_time, end_time;
double elapsed_time;
double write_speed, read_speed;
// 分配缓冲区
buffer = (char *)malloc(BUFFER_SIZE);
if (buffer == NULL) {
perror("内存分配失败");
return 1;
}
memset(buffer, 'A', BUFFER_SIZE);
// 打开文件用于写入
fd = open(FILENAME, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd == -1) {
perror("打开文件用于写入失败");
free(buffer);
return 1;
}
// 记录写操作开始时间
start_time = clock();
// 进行写操作
bytes_written = write(fd, buffer, BUFFER_SIZE);
if (bytes_written == -1) {
perror("写操作失败");
close(fd);
free(buffer);
return 1;
}
// 记录写操作结束时间
end_time = clock();
// 计算写操作用时
elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
// 计算写速度(MB/s)
write_speed = (double)bytes_written / elapsed_time / (1024 * 1024);
// 输出写操作信息
printf("同步写操作完成,传输了 %zd 字节,用时 %.6f 秒,速度 %.2f MB/s\n", bytes_written, elapsed_time, write_speed);
// 关闭文件
close(fd);
// 打开文件用于读取
fd = open(FILENAME, O_RDONLY);
if (fd == -1) {
perror("打开文件用于读取失败");
free(buffer);
return 1;
}
// 记录读操作开始时间
start_time = clock();
// 进行读操作
bytes_read = read(fd, buffer, BUFFER_SIZE);
if (bytes_read == -1) {
perror("读操作失败");
close(fd);
free(buffer);
return 1;
}
// 记录读操作结束时间
end_time = clock();
// 计算读操作用时
elapsed_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
// 计算读速度(MB/s)
read_speed = (double)bytes_read / elapsed_time / (1024 * 1024);
// 输出读操作信息
printf("同步读操作完成,传输了 %zd 字节,用时 %.6f 秒,速度 %.2f MB/s\n", bytes_read, elapsed_time, read_speed);
// 关闭文件
close(fd);
// 释放缓冲区
free(buffer);
return 0;
}
结果对比
我们编译两个程序然后运行看一下结果

我们可以看出在大数据传输情况下,异步操作好像更快一些。
程序区别分析
同步和异步操作在文件读写中的主要区别在于程序在进行 I/O 操作时的行为模式,下面来详细分析这两个程序的差异以及区分同步和异步的关键部分。
整体区别概述
同步操作:在同步文件读写中,当程序发起一个 I/O 请求(如 `read` 或 `write`)时,程序会被阻塞,直到该请求完成。也就是说,在 I/O 操作进行期间,程序无法执行其他任务,只能等待操作结束。
异步操作:而异步文件读写允许程序在发起 I/O 请求后继续执行其他任务,无需等待 I/O 操作完成。当 I/O 操作完成时,程序会通过某种机制(如回调函数)得到通知。
具体代码差异及关键区分部分1. 读写操作的发起和执行方式
同步程序
// 同步写操作
start_time = clock();
bytes_written = write(fd, buffer, BUFFER_SIZE);
end_time = clock();
// 同步读操作
start_time = clock();
bytes_read = read(fd, buffer, BUFFER_SIZE);
end_time = clock();
ps:write函数结构体
ssize_t write(int fd, const void *buf, size_t count);
fd :这是一个整数类型的文件描述符
buf :这是一个指向要写入数据的缓冲区的指针。
count :这是一个 size_t 类型的参数,表示要写入的字节数。
在同步程序中,`write` 和 `read` 函数是阻塞调用。当调用 `write` 函数时,程序会暂停执行,直到数据全部写入文件;调用 `read` 函数时,程序会等待直到从文件中读取到指定数量的数据。在这个过程中,CPU 处于等待状态,无法处理其他任务。
异步程序
// 异步写操作
write_start_time = clock();
if (aio_write(&aiocb) == -1) {
perror("发起异步写操作失败");
free(buffer);
close(fd);
return 1;
}
// 异步读操作
read_start_time = clock();
if (aio_read(&aiocb) == -1) {
perror("发起异步读操作失败");
free(buffer);
close(fd);
return 1;
}
在异步程序中,`aio_write` 和 `aio_read` 函数是非阻塞调用。当调用这些函数时,程序会立即返回,不会等待 I/O 操作完成。程序可以继续执行后续的代码,而 I/O 操作会在后台进行。2. 操作完成的通知和等待机制
同步程序
同步程序不需要额外的通知机制,因为 `write` 和 `read` 函数返回时,操作已经完成。程序通过检查返回值来判断操作是否成功。
if (bytes_written == -1) {
perror("写操作失败");
close(fd);
free(buffer);
return 1;
}
if (bytes_read == -1) {
perror("读操作失败");
close(fd);
free(buffer);
return 1;
}
异步程序
异步程序使用 `aio_error` 函数来检查 I/O 操作的状态,并通过循环等待操作完成。当操作完成时,会调用回调函数 `aio_complete_handler` 进行处理。
// 等待写操作完成
int max_attempts = 10000;
int attempts = 0;
while (aio_error(&aiocb) == EINPROGRESS && attempts < max_attempts) {
usleep(1000);
attempts++;
}
// 等待读操作完成
attempts = 0;
while (aio_error(&aiocb) == EINPROGRESS && attempts < max_attempts) {
usleep(1000);
attempts++;
}
同时,在初始化 `aiocb` 结构体时,设置了回调函数:
aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
aiocb.aio_sigevent.sigev_notify_function = aio_complete_handler;
当 I/O 操作完成时,会启动一个新线程调用 `aio_complete_handler` 函数,程序可以在这个函数中处理操作结果。
总结
区分同步和异步的关键在于 I/O 操作的发起和执行方式以及操作完成的通知和等待机制。同步操作是阻塞的,程序需要等待操作完成;而异步操作是非阻塞的,程序可以继续执行其他任务,通过回调或轮询来处理操作结果。
2026

被折叠的 条评论
为什么被折叠?



