异步IO浅析之四:异步IO的单个处理和批量处理

简介:

异步IO由于它的非阻塞特性和强大的并发能力,非常适合用在要求高并发和高吞吐率的场景,比如用在提供SAN存储的块设备读写的实现上。和传统IO模式类似,异步IO提供了一次提交一个IO请求的模式,还提供了一次提交一组IO请求的方式。下面将分别介绍这两种模式的使用方法和差异。

 

  1. 单个处理模式


下面对一个长度为BUF_LEN的buffer,提交了MAX_IO个单个处理的IO请求,每个请求处理BUF_LEN/MAX_IO这么长的数据。为了便于读者实际上机验证,下面贴出了具体处理的代码:

..............................................................................................

int main(int argc, char * argv[])

{

    io_context_t ctx_id = 0;

    struct iocb  mycb[MAX_AIO];

    struct iovec myiov[MAX_AIO];

    struct io_event events[MAX_AIO];

    const int aioto = 5000000;

    io_callback_t cb = my_callback;

    unsigned nr_events = MAX_AIO;

    int i, lenperIo, done, cnt = 0;

    char buf[BUF_LEN];

    memset(mycb, 0, sizeof(mycb));

    memset(myiov, 0, sizeof(myiov));

    int fd = open("./text.txt", O_CREAT | O_RDWR);

    assert(fd >= 0);

    memset(buf, 'a', BUF_LEN);

    io_setup(nr_events, &ctx_id);

    lenperIo = BUF_LEN/MAX_AIO;

    for (i = 0; i < MAX_AIO; i++) {

        io_prep_pwrite(&mycb[i], fd, lenperIo * i +  (char *)buf, lenperIo, lenperIo * i);

        io_set_callback(&mycb[i], my_callback);

        mycb[i].key = i;

        events[i].obj = &mycb[i];

    }

    struct timespec ts;

    ts.tv_sec = 0;

    ts.tv_nsec = aioto * 1000;

    while (cnt < MAX_AIO) {

        done = io_getevents(ctx_id, 1, MAX_AIO, events, &ts);

        cnt += done;

        printf("Finish %d coming IO!\n", done);

        sleep(1);

    }

    for (i = 0; i < MAX_AIO; i++) {

        cb = (io_callback_t)(events[i].data);

        cb(ctx_id, (struct iocb *)events[i].obj, i, 0);

        printf("events[%d]: obj = %p res = %ld res2 = %ld\n", i, events[i].obj, events[i].res, events[i].res2);

    }

    printf("My callback address : %p\n", (void *)my_callback);

    close(fd);

}

..............................................................................................

可以看到,上面是多次提交io_prep_pwrite(),然后通过检查是否收到相应数目的event来判断IO是否完成。

 

2. 批量处理模式

 

初始化的代码差不多,主要的差别代码在下面

..............................................................................................

 io_setup(nr_events, &ctx_id);

    lenperIo = BUF_LEN/MAX_AIO;

    for (i = 0; i < MAX_AIO; i++) {

        myiov[i].iov_len = lenperIo;

        myiov[i].iov_base = lenperIo * i +  (char *)buf;

        pmycb[i] = &mycb[i];

    }

    io_prep_pwritev(&mycb[0], fd, myiov, MAX_AIO, 32);

    io_set_callback(&mycb[0], cb);

    struct timespec ts;

    ts.tv_sec = 0;

    ts.tv_nsec = aioto * 1000;

    while (cnt < 1) {

        done = io_getevents(ctx_id, 1, 1, events, &ts);

        cnt += done;

        printf("Finish %d coming IO!\n", done);

        sleep(2);

    }

..............................................................................................

可以看到,在批量处理模式,先需要初始化一个io vector数组,在数组里面再指定IO操作在内存中的数据起始地址和长度,然后调用一次io_prep_pweritev(),最后等待唯一的一个event就可以了。

 

3. 两种处理方式的比较


下面的表格,总结了使用上面两种处理方式完成相同IO任务的实现上的差异:

模式

使用函数

是否必需用iovec

io_setup调用次数

IO提交函数调用次数

io_getevents

需调用的次数

生成的io events数目

单个处理

 

io_prep_pwrite

io_prep_pread

不是

多次

多次

很可能多次

多个

批量处理

io_prep_pwritev

io_prep_preadv

1

1

最少一次


这里常常容易混淆的地方就是误以为每个io操作对于一个IO event,其实不然:是每个iocb对应一个IO event, 因为它IO event数据结构内部的obj和callback 都只有一份和iocb 数据结构里面的相对应,关于这点的详细说明可以参考我关于异步IO的上篇博客《C中异步IO浅析之三:深入理解异步IO的基本数据结构》。

 

4. 注意事项


单个IO处理的模式很好理解,而对批量处理,个人认为有一处man手册和头文件中都没有说明白的地方,那就是: io_prep_pwritev/ io_prep_preadv函数的最后一个参数offset的含义,它表示的是io vecotr里面最早执行的那个IO开始执行时读写操作在磁盘或文件上的物理偏移, 而下一个IO在磁盘或文件上读写的起始地址,就是这个偏移再加上刚完成IO操作的长度。因此,IO操作的总长度是IO vector里面所有成员的iov_len字段之和。

 

















本文转自存储之厨51CTO博客,原文链接: http://blog.51cto.com/xiamachao/1977588,如需转载请自行联系原作者
相关文章
|
1月前
|
并行计算 数据处理 Python
Python并发编程迷雾:IO密集型为何偏爱异步?CPU密集型又该如何应对?
在Python的并发编程世界中,没有万能的解决方案,只有最适合特定场景的方法。希望本文能够为你拨开迷雾,找到那条通往高效并发编程的光明大道。
42 2
|
2月前
|
开发框架 并行计算 算法
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
46 4
|
6月前
|
调度 数据库 Python
【专栏】异步IO在处理IO密集型任务中的高效性
【4月更文挑战第27天】本文介绍了Python并发编程和异步IO,包括并发的基本概念(多线程、多进程、协程),线程与进程的实现(threading和multiprocessing模块),协程的使用(asyncio模块),以及异步IO的原理和优势。强调了异步IO在处理IO密集型任务中的高效性,指出应根据任务类型选择合适的并发技术。
158 2
|
2月前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
在数据驱动时代,高效处理大规模数据和高并发请求至关重要。Python凭借其优雅的语法和强大的库支持,成为开发者首选。本文将介绍Python中的并发与异步编程,涵盖并发与异步的基本概念、IO密集型任务的并发策略、CPU密集型任务的并发策略以及异步IO的应用。通过具体示例,展示如何使用`concurrent.futures`、`asyncio`和`multiprocessing`等库提升程序性能,帮助开发者构建高效、可扩展的应用程序。
112 0
|
4月前
|
并行计算 数据处理 Python
Python并发编程迷雾:IO密集型为何偏爱异步?CPU密集型又该如何应对?
【7月更文挑战第17天】Python并发编程中,异步编程(如`asyncio`)在IO密集型任务中提高效率,利用等待时间执行其他任务。但对CPU密集型任务,由于GIL限制,多线程效率不高,此时应选用`multiprocessing`进行多进程并行计算以突破限制。选择合适的并发策略是关键:异步适合IO,多进程适合CPU。理解这些能帮助构建高效并发程序。
116 6
|
4月前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
【7月更文挑战第17天】在数据驱动时代,Python凭借其优雅语法和强大库支持成为并发处理大规模数据的首选。并发与异步编程是关键,包括多线程、多进程和异步IO。对于IO密集型任务,如网络请求,可使用`concurrent.futures`和`asyncio`;CPU密集型任务则推荐多进程,如`multiprocessing`;`asyncio`适用于混合任务,实现等待IO时执行CPU任务。通过这些工具,开发者能有效优化资源,提升系统性能。
93 4
|
4月前
|
开发框架 并行计算 .NET
从菜鸟到大神:Python并发编程深度剖析,IO与CPU的异步战争!
【7月更文挑战第18天】Python并发涉及多线程、多进程和异步IO(asyncio)。异步IO适合IO密集型任务,如并发HTTP请求,能避免等待提高效率。多进程在CPU密集型任务中更优,因可绕过GIL限制实现并行计算。通过正确选择并发策略,开发者能提升应用性能和响应速度。
106 3
|
4月前
|
开发框架 并行计算 算法
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
【7月更文挑战第18天】Python并发编程中,异步IO适合IO密集型任务,如异步HTTP请求,利用`asyncio`和`aiohttp`实现并发抓取,避免等待延迟。而对于CPU密集型任务,如并行计算斐波那契数列,多进程通过`multiprocessing`库能绕过GIL限制实现并行计算。选择正确的并发模型能显著提升性能。
88 2
|
4月前
|
开发框架 数据挖掘 .NET
显微镜下的Python并发:细说IO与CPU密集型任务的异步差异,助你精准施策!
【7月更文挑战第16天】在Python并发编程中,理解和区分IO密集型与CPU密集型任务至关重要。IO密集型任务(如网络请求)适合使用异步编程(如`asyncio`),以利用等待时间执行其他任务,提高效率。CPU密集型任务(如计算)则推荐使用多进程(如`multiprocessing`),绕过GIL限制,利用多核CPU。正确选择并发策略能优化应用性能。
71 2
|
4月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用问题之在Flink算子内部使用异步IO可以通过什么办法实现
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。