在 Windows 平台上不可用。

Libeio是全功能的用于C语言的异步I/O库,建模风格和秉承的精神与libev类似。特性包括:异步的read、write、open、close、stat、unlink、fdatasync、mknod、readdir等(基本上是完整的POSIX API)。

Libeio完全基于事件库,可以容易地集成到事件库(或独立,甚至是以轮询方式)使用。Libeio非常轻便,且只依赖于POSIX线程。

Libeio当前的源码,文档,集成和轻便性都在libev之下,但应该很快可以用于生产环境了。

Libeio是用多线程实现的异步I/O库.主要步骤如下:

  1. 主线程接受请求,将请求放入请求队列,唤醒子线程处理。这里主线程不会阻塞,会继续接受请求
  2. 子线程处理请求,将请求回执放入回执队列,并调用用户自定义方法,通知主线程有请求已处理完毕
  3. 主线程处理回执。

源码中提供了一个demo.c用于演示,精简代码如下:

[cpp] view plaincopyprint?
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <poll.h>
  5. #include <string.h>
  6. #include <assert.h>
  7. #include <fcntl.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include "eio.h"
  11. int respipe [2];
  12. /*
  13. * 功能:子线程通知主线程已有回执放入回执队列.
  14. */
  15. void
  16. want_poll (void)
  17. {
  18. char dummy;
  19. printf ("want_poll ()\n");
  20. write (respipe [1], &dummy, 1);
  21. }
  22. /*
  23. * 功能:主线程回执处理完毕,调用此函数
  24. */
  25. void
  26. done_poll (void)
  27. {
  28. char dummy;
  29. printf ("done_poll ()\n");
  30. read (respipe [0], &dummy, 1);
  31. }
  32. /*
  33. * 功能:等到管道可读,处理回执信息
  34. */
  35. void
  36. event_loop (void)
  37. {
  38. // an event loop. yeah.
  39. struct pollfd pfd;
  40. pfd.fd     = respipe [0];
  41. pfd.events = POLLIN;
  42. printf ("\nentering event loop\n");
  43. while (eio_nreqs ())
  44. {
  45. poll (&pfd, 1, -1);
  46. printf ("eio_poll () = %d\n", eio_poll ());
  47. }
  48. printf ("leaving event loop\n");
  49. }
  50. /*
  51. * 功能:自定义函数,用户处理请求执行后的回执信息
  52. */
  53. int
  54. res_cb (eio_req *req)
  55. {
  56. printf ("res_cb(%d|%s) = %d\n", req->type, req->data ? req->data : "?", EIO_RESULT (req));
  57. if (req->result < 0)
  58. abort ();
  59. return 0;
  60. }
  61. int
  62. main (void)
  63. {
  64. printf ("pipe ()\n");
  65. if (pipe (respipe))
  66. abort ();
  67. printf ("eio_init ()\n");
  68. if (eio_init (want_poll, done_poll)) //初始化libeio库
  69. abort ();
  70. eio_mkdir ("eio-test-dir", 0777, 0, res_cb, "mkdir");
  71. event_loop ();
  72. return 0;
  73. }

可以将demo.c与libeio一起编译,也可以先将libeio编译为动态链接库,然后demo.c与动态链接库一起编译。

执行流程图如下所示:

流程图详细步骤说明如下:

1、通过pipe函数创建管道。

管道主要作用是子线程告知父线程已有请求回执放入回执队列,父线程可以进行相应的处理。

2.   libeio执行初始化操作。

调用eio_init执行初始化。eio_init函数声明:int eio_init (void (*want_poll)(void), void (*done_poll)(void))。eio_init参数是两个函数指针,want_poll和done_poll是成对出现。want_poll主要是子线程通知父线程已有请求处理完毕,done_poll则是在所有请求处理完毕后调用。

eio_init代码如下:

[cpp] view plaincopyprint?
  1. /*
  2. * 功能:libeio初始化
  3. */
  4. static int ecb_cold
  5. etp_init (void (*want_poll)(void), void (*done_poll)(void))
  6. {
  7. X_MUTEX_CREATE (wrklock);//子线程队列互斥量
  8. X_MUTEX_CREATE (reslock);//请求队列互斥量
  9. X_MUTEX_CREATE (reqlock);//回执队列互斥量
  10. X_COND_CREATE  (reqwait);//创建条件变量
  11. reqq_init (&req_queue);//初始化请求队列
  12. reqq_init (&res_queue);//初始化回执队列
  13. wrk_first.next =
  14. wrk_first.prev = &wrk_first;//子线程队列
  15. started  = 0;//运行线程数
  16. idle     = 0;//空闲线程数
  17. nreqs    = 0;//请求任务个数
  18. nready   = 0;//待处理任务个数
  19. npending = 0;//未处理的回执个数
  20. want_poll_cb = want_poll;
  21. done_poll_cb = done_poll;
  22. return 0;
  23. }

3、父线程接受I/O请求

实例IO请求为创建一个文件夹。一般I/O请求都是阻塞请求,即父线程需要等到该I/O请求执行完毕,才能进行下一步动作。在libeio里面,主线程无需等待I/O操作执行完毕,它可以做其他事情,如继续接受I/O请求。

这里创建文件夹,调用的libeio中的方法eio_mkdir。libeio对常用的I/O操作,都有自己的封装函数。

[cpp] view plaincopyprint?
  1. eio_req *eio_wd_open   (const char *path, int pri, eio_cb cb, void *data); /* result=wd */
  2. eio_req *eio_wd_close  (eio_wd wd, int pri, eio_cb cb, void *data);
  3. eio_req *eio_nop       (int pri, eio_cb cb, void *data); /* does nothing except go through the whole process */
  4. eio_req *eio_busy      (eio_tstamp delay, int pri, eio_cb cb, void *data); /* ties a thread for this long, simulating busyness */
  5. eio_req *eio_sync      (int pri, eio_cb cb, void *data);
  6. eio_req *eio_fsync     (int fd, int pri, eio_cb cb, void *data);
  7. eio_req *eio_fdatasync (int fd, int pri, eio_cb cb, void *data);
  8. eio_req *eio_syncfs    (int fd, int pri, eio_cb cb, void *data);
  9. eio_req *eio_msync     (void *addr, size_t length, int flags, int pri, eio_cb cb, void *data);
  10. eio_req *eio_mtouch    (void *addr, size_t length, int flags, int pri, eio_cb cb, void *data);
  11. eio_req *eio_mlock     (void *addr, size_t length, int pri, eio_cb cb, void *data);
  12. eio_req *eio_mlockall  (int flags, int pri, eio_cb cb, void *data);
  13. eio_req *eio_sync_file_range (int fd, off_t offset, size_t nbytes, unsigned int flags, int pri, eio_cb cb, void *data);
  14. eio_req *eio_fallocate (int fd, int mode, off_t offset, size_t len, int pri, eio_cb cb, void *data);
  15. eio_req *eio_close     (int fd, int pri, eio_cb cb, void *data);
  16. eio_req *eio_readahead (int fd, off_t offset, size_t length, int pri, eio_cb cb, void *data);
  17. eio_req *eio_seek      (int fd, off_t offset, int whence, int pri, eio_cb cb, void *data);
  18. eio_req *eio_read      (int fd, void *buf, size_t length, off_t offset, int pri, eio_cb cb, void *data);
  19. eio_req *eio_write     (int fd, void *buf, size_t length, off_t offset, int pri, eio_cb cb, void *data);

从列举的函数中可以看出一些共同点,

  • 返回值相同,都是结构体eio_req指针。
  • 函数最后三个参数都一致。pri表示优先级;cb是用户自定义的函数指针,主线程在I/O完成后调用;data存放数据

这里需要指出的是,在这些操作里面,没有执行真正的I/O操作。下面通过eio_mkdir源码来说明这些函数到底做了什么?

[cpp] view plaincopyprint?
  1. /*
  2. * 功能:将创建文件夹请求放入请求队列
  3. */
  4. eio_req *eio_mkdir (const char *path, mode_t mode, int pri, eio_cb cb, void *data)
  5. {
  6. REQ (EIO_MKDIR);
  7. PATH;
  8. req->int2 = (long)mode;
  9. SEND;
  10. }

不得不吐槽一下,libeio里面太多宏定义了,代码风格有点不好。这里REQ,PATH,SEND都是宏定义。为了便于阅读,把宏给去掉

[cpp] view plaincopyprint?
  1. /*
  2. * 功能:将创建文件夹请求放入请求队列
  3. */
  4. eio_req *eio_mkdir (const char *path, mode_t mode, int pri, eio_cb cb, void *data)
  5. {
  6. eio_req *req;
  7. req = (eio_req *)calloc (1, sizeof *req);
  8. if (!req)
  9. return 0;
  10. req->type    = EIO_MKDIR;// 请求类型
  11. req->pri     = pri;//请求优先级
  12. req->finish  = cb;//请求处理完成后调用的函数
  13. req->data    = data;//用户数据
  14. req->destroy = eio_api_destroy;//释放req资源
  15. req->flags |= EIO_FLAG_PTR1_FREE;//标记需要释放ptr1
  16. req->ptr1 = strdup (path);
  17. if (!req->ptr1)
  18. {
  19. eio_api_destroy (req);
  20. return 0;
  21. }
  22. req->int2 = (long)mode;
  23. eio_submit (req); //将请求放入请求队列,并唤醒子线程
  24. return req;
  25. }

4、请求放入请求队列

请求队列由结构体指针数组qs,qe构成,数组大小为9,数组的序号标志了优先级,即qs[1]存放的是优先级为1的所有请求中的第一个,qe[1]存放的是优先级为1的所有请求的最后一个。这样做的好处是,在时间复杂度为O(1)的情况下插入新的请求。

[cpp] view plaincopyprint?
  1. /*
  2. * 功能:将请求放入请求队列,或者将回执放入回执队列。 qe存放链表终点.qs存放链表起点.
  3. */
  4. static int ecb_noinline
  5. reqq_push (etp_reqq *q, ETP_REQ *req)
  6. {
  7. int pri = req->pri;
  8. req->next = 0;
  9. if (q->qe[pri])//如果该优先级以后请求,则插入到最后
  10. {
  11. q->qe[pri]->next = req;
  12. q->qe[pri] = req;
  13. }
  14. else
  15. q->qe[pri] = q->qs[pri] = req;
  16. return q->size++;
  17. }

5、唤醒子线程

这里并不是来一个请求,就为该请求创建一个线程。在下面两种情况下,不创建线程。
  • 创建的线程总数大于4(这个数字要想改变,只有重新编译libeio了)
  • 线程数大于未处理的请求。

线程创建之后,放入线程队列。

[cpp] view plaincopyprint?
  1. /*
  2. * 功能:创建线程,并把线程放入线程队列
  3. */
  4. static void ecb_cold
  5. etp_start_thread (void)
  6. {
  7. etp_worker *wrk = calloc (1, sizeof (etp_worker));
  8. /*TODO*/
  9. assert (("unable to allocate worker thread data", wrk));
  10. X_LOCK (wrklock);
  11. //创建线程,并将线程插入到线程队列.
  12. if (thread_create (&wrk->tid, etp_proc, (void *)wrk))
  13. {
  14. wrk->prev = &wrk_first;
  15. wrk->next = wrk_first.next;
  16. wrk_first.next->prev = wrk;
  17. wrk_first.next = wrk;
  18. ++started;
  19. }
  20. else
  21. free (wrk);
  22. X_UNLOCK (wrklock);
  23. }

6、子线程从请求队列中取下请求

取请求时按照优先级来取的。

7、子线程处理请求

子线程调用eio_excute处理请求。这里才真正的执行I/O操作。之前我们传过来的是创建文件夹操作,子线程判断请求类型,根据类型,调用系统函数执行操作,并把执行结果,写回到请求的result字段,如果执行有误,设置errno
因为eio_excute函数比较长,这里只贴出创建文件夹代码。
[cpp] view plaincopyprint?
  1. /*
  2. * 功能:根据类型,执行不同的io操作
  3. */
  4. static void
  5. eio_execute (etp_worker *self, eio_req *req)
  6. {
  7. #if HAVE_AT
  8. int dirfd;
  9. #else
  10. const char *path;
  11. #endif
  12. if (ecb_expect_false (EIO_CANCELLED (req)))//判断该请求是否取消
  13. {
  14. req->result  = -1;
  15. req->errorno = ECANCELED;
  16. return;
  17. }
  18. switch (req->type)
  19. {
  20. case EIO_MKDIR:     req->result = mkdirat   (dirfd, req->ptr1, (mode_t)req->int2); break;
  21. }
  22. }

从代码中可以看出,用户是可以取消之前的I/O操作,如果I/O操作未执行,可以取消。如果I/O操作已经在运行了,则取消无效。

8、写回执

回执其实就是之前传给子线程的自定义结构体。当子线程取下该请求,并根据类型执行后,执行结构写入请求的result字段,并将该请求插入到回执队列res_queue中。

9、通知父线程有回执

用户自己定义want_poll函数,用于子线程通知父线程有请求回执放入回执队列。示例代码是用的写管道。这里需要指出的时,当将请求回执放入空的回执 队列才会通知父线程,如果在放入时,回执队列已不为空,则不会通知父线程。为什么了?因为父线程处理回执的时候,会处理现有的所有回执。
[cpp] view plaincopyprint?
  1. /*
  2. * 功能:子线程通知主线程已有回执放入回执队列.
  3. */
  4. void
  5. want_poll (void)
  6. {
  7. char dummy;
  8. printf ("want_poll ()\n");
  9. write (respipe [1], &dummy, 1);
  10. }

10、父线程处理回执

调用eio_poll函数处理回执。或许看到这里你在想,eio_poll是个系统函数,我们没办法修改,但是我们如何知道每一个I/O请求执行结果。 其实还是用的函数指针,在我们构建一个I/O请求结构体时,有一个finsh函数指针。当父进程处理I/O回执时,会调用该方法。这里自定义的 finish函数名为res_cb,当创建文件夹成功后,调用该函数,输出一句话

[cpp] view plaincopyprint?
  1. /*
  2. * 功能:处理回执
  3. */
  4. static int
  5. etp_poll (void)
  6. {
  7. unsigned int maxreqs;
  8. unsigned int maxtime;
  9. struct timeval tv_start, tv_now;
  10. X_LOCK (reslock);
  11. maxreqs = max_poll_reqs;
  12. maxtime = max_poll_time;
  13. X_UNLOCK (reslock);
  14. if (maxtime)
  15. gettimeofday (&tv_start, 0);
  16. for (;;)
  17. {
  18. ETP_REQ *req;
  19. etp_maybe_start_thread ();
  20. X_LOCK (reslock);
  21. req = reqq_shift (&res_queue);//从回执队列取出优先级最高的回执信息
  22. if (req)
  23. {
  24. --npending;
  25. if (!res_queue.size && done_poll_cb)//直到回执全部处理完,执行done_poll();
  26. {
  27. //printf("执行done_poll()\n");
  28. done_poll_cb ();
  29. }
  30. }
  31. X_UNLOCK (reslock);
  32. if (!req)
  33. return 0;
  34. X_LOCK (reqlock);
  35. --nreqs;//发出请求,到收到回执,该请求才算处理完毕.
  36. X_UNLOCK (reqlock);
  37. if (ecb_expect_false (req->type == EIO_GROUP && req->size))//ecb_expect_false仅仅用于帮助编译器产生更优代码,而对真值无任何影响
  38. {
  39. req->int1 = 1; /* mark request as delayed */
  40. continue;
  41. }
  42. else
  43. {
  44. int res = ETP_FINISH (req);//调用自定义函数,做进一步处理
  45. if (ecb_expect_false (res))
  46. return res;
  47. }
  48. if (ecb_expect_false (maxreqs && !--maxreqs))
  49. break;
  50. if (maxtime)
  51. {
  52. gettimeofday (&tv_now, 0);
  53. if (tvdiff (&tv_start, &tv_now) >= maxtime)
  54. break;
  55. }
  56. }
  57. errno = EAGAIN;
  58. return -1;
  59. }

11、当所有请求执行完毕,调用done_poll做收尾工作。

在示例代码中是读出管道中的数据。用户可以自己定义一些别的工作
[cpp] view plaincopyprint?
  1. /*
  2. * 功能:主线程回执处理完毕,调用此函数
  3. */
  4. void
  5. done_poll (void)
  6. {
  7. char dummy;
  8. printf ("done_poll ()\n");
  9. read (respipe [0], &dummy, 1);
  10. }

至此,libeio就简单的跑了一遍,从示例代码可以看出,libeio使用简单。虽说现在是beat版,不过Node.js已经在使用了。

最后简单说一下代码中的宏ecb_expect_false和ecb_expect_true,在if判断中,经常会出现这两个宏,一步一步的查看宏定义,宏定义如下:
[cpp] view plaincopyprint?
  1. #define ecb_expect(expr,value)         __builtin_expect ((expr),(value))
  2. #define ecb_expect_false(expr) ecb_expect (!!(expr), 0)
  3. #define ecb_expect_true(expr)  ecb_expect (!!(expr), 1)
  4. /* for compatibility to the rest of the world */
  5. #define ecb_likely(expr)   ecb_expect_true  (expr)
  6. #define ecb_unlikely(expr) ecb_expect_false (expr)

刚开始我也不太懂啥意思,后来查阅资料(http://www.adamjiang.com/archives/251)才明白,这些宏仅仅是在帮助编译器产生更优代码,而对真值的判断没有影响

libeio-异步I/O库初窥相关推荐

  1. 初窥R(基本说明、获取帮助、工作空间、输入输出、包)

    初窥R(基本说明.获取帮助.工作空间.输入输出.包) 本篇简要介绍使用R的一些基本概念,包括基本说明.获取帮助.工作空间.输入输出,每个知识点中都会通过一个例子来练习. 一.R基本情况说明 1.R是一 ...

  2. 【重点】初窥Linux 之 我最常用的20多条命令

    [重点]初窥Linux 之 我最常用的20多条命令 玩过Linux的人都会知道,Linux中的命令的确是非常多,但是玩过Linux的人也从来不会因为Linux的命令如此之多而烦恼,因为我们只需要掌握我 ...

  3. 初窥Xcode4 -- Xcode4主题样式、快捷键等常用设置

    今天晚上开始使用Xcode4,初窥历程如下. 一.下载安装Xcode4 因为我需要让Xcode4和老版本共存,在安装Xcode4时选择其他安装路径,安装成功后在xcode4安装目录下改名Xcode.a ...

  4. Linux 计算机网络 从 ping 来初窥计算机网络

    Linux 计算机网络 从 ping 来初窥计算机网络 在上一章节<计算机网络从零到一>我们重点讲解了整个网络的形成,以及物理层.数据链路层.网络层这三层的形成以及他们所解决的问题,而本章 ...

  5. 初窥werkzeug

    初窥werkzeug 什么是werkzeug 首先要明确一点werkzeug不是一个WEB服务器也不是一个WEB框架, 它是一个WSGI的工具包, 它可以作为一个WEB框架的底层库被使用, 而Flas ...

  6. Three.js初窥

    随着当今时期前端地愈来愈普及,页面实现的效果真的是越来越棒! 随着数字图像处理.人工智能技术的发展,展示给用户的视觉效果便不局限于平面的2D视觉效果,开始注重于全方位的3D立体展示效果,力求对于商品的 ...

  7. OBJECT_METHOD初窥

    一.  背景:Windows NT 的对象机制 Windows NT系统将各种资源以对象的方式进行组织和管理.虽然Windows NT内核使用C语言和汇编语言编写的,本身并未使用到C++中的面向对象机 ...

  8. 并发编程-01并发初窥

    文章目录 引言 思维导图 基础知识构建 涉及的知识点一览 高并发处理思路与手段一览 并发初窥 概念 并发问题模拟 代码 引言 说来惭愧,一直没有系统的梳理过并发编程的知识,这次借着学习_Jimin_老 ...

  9. java aspectj_初窥AspectJ

    AspectJ可以说是Java中当之无愧的黑魔法.说它是黑魔法,一方面是因为它很强大,能够解决一些传统编程方法论解决不了的问题,而另一方面,它也相当的晦涩,有着比较陡峭的学习曲线. 本文将带大家探索下 ...

最新文章

  1. Windows10 编译 Open3D 时出现 error C2220: 以下警告被视为错误 (编译源文件
  2. Elasticsearch 安装配置 外网访问 及 后台启动
  3. C# 分割字符串方法
  4. 清华博导尹首一, 带你吃透 AI 芯片来龙去脉!
  5. 处理服务器故障的前5分钟(转)
  6. javascript 权威指南一
  7. 计算机网络电缆是什么,插入Cat的计算机网络电缆和插入路由器的计算机的网络电缆有什么区别?...
  8. stem函数--Matplotlib
  9. axure8 事件改变样式_AxureRP8实战手册-案例2(文本框:边框变色)
  10. python网络登录脚本_Python实现登录人人网并抓取新鲜事的方法
  11. 目的路径太长如哈删除_文件名超长导致文件无法删除怎么办?
  12. 双系统android,如何在Android手机上实现双系统
  13. 10个常用的数据分析商业模型之漏斗模型(六)
  14. 生活时尚酒店品牌JOJOE进军中国,首批选址7个城市,未来将开设至少1300家门店 | 美通社头条...
  15. android ios 实时视频,Twitter推出适用于Android和iOS设备的实时视频
  16. “代理服务出现问题,或者地址有误“解决方案
  17. 二分 1238:一元三次方程求解
  18. 用OSM数据画地图(含代码)
  19. Vjudge B - Grandpa is Famous
  20. halcon中如何生成椭圆_教你动态生成椭圆,还教你怎么用海龟作图——GeoGebra制作教程...

热门文章

  1. windows API 菜鸟学习之路(一)
  2. 大数据之-Hadoop3.x_MapReduce_序列化案例FlowMapper---大数据之hadoop3.x工作笔记0098
  3. 大数据之-Hadoop3.x_MapReduce_序列化案例需求分析---大数据之hadoop3.x工作笔记0096
  4. Hive大数据-Hive的架构---大数据之Hive工作笔记0003
  5. Netty工作笔记0085---TCP粘包拆包内容梳理
  6. PostGreSql学习笔记001---PostgreSQL10.4安装(Windows)_支持PostGreGis_PostJDBC
  7. 1.C#基础学习笔记3---C#字符串(转义符和内存存储无关)
  8. 经典线程同步 关键段CS
  9. MFC无法使用CDialogEx类
  10. WIN32_FIND_DATA