• 如何理解高效IO


    目录

    前言

    1.如何理解高效的IO

    2.五种IO模型

    3.非阻塞IO

    4.非阻塞代码编写

    总结 


    前言

            哈喽,很高兴和大家见面!今天我们要介绍的关于IO的话题,在计算机中IO是非常常规的操作,例如将数据显示到外设,或者将数据从主机A发送到主机B……为了提高性能,减少IO的时间成为了一个人们比较关心的话题,而今天我们要介绍的是如何做才能提高IO效率。

    1.如何理解高效的IO

    IO:本质上是将数据从一方拷贝到另一方例如:调用read/recv读数据, 本质是将缓冲区的数据拷贝一份!但是拷贝数据的前提是要有数据,所以IO的过程包含两部分,一部分是等数据准备好,另一部分才是拷贝数据。因为拷贝数据的效率是由硬件本身决定的,所以要做到高效的IO,是要减少等的时间!

    2.五种IO模型

    举例说明:钓鱼的例子
    张三坐在河边一动不动的等着鱼上钩
    李四将鱼竿放在河边,然后就去做别的事情,然后隔一段时间看看有没有鱼上钩
    五在鱼竿上放一个铃铛,将鱼竿放在河边,然后就去做别的事情,等铃铛响了就说明有鱼上钩了,然后把鱼钓上来
    赵六带了一群鱼竿,然后都放在河边,然后就轮询的查看是否有鱼上钩
    田七:找了一个人小刘,帮他钓鱼,等鱼钓上来,小刘通知田七,然后让田七把鱼拿走

    其中张三对应于阻塞式IO
    李四对应非阻塞式IO
    王五对应信号驱动式IO
    赵六对应多路转接/多路复用IO
    田七对应异步IO
    其中张三,李四,王五在效率上并没有什么差别!从整齐上来看李四和王五在钓鱼期间可以做其它的事情
    信号驱动式IO:虽然是等信号发送了之后才会去拷贝数据,但是本质上也是等了!

    四种方式,每个人都等了钓鱼->属于同步IO
    第五种方式,没有参与IO阶段中任何阶段->属于异步IO
    对于阻塞式IO和非阻塞式IO的差别:
    相同点:都会进行数据拷贝
    不同点:等的方式不同

    阻塞式IO模型:

    在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式

    非阻塞式IO模型:

    如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码.
    非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用.

    信号驱动式IO模型:

    内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作

    IO多路转接模型:

    虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件
    描述符的就绪状态. 

    异步IO:

    由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据). 

    小结
    任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少. 

    3.非阻塞IO

    一个文件描述符, 默认都是阻塞IO.

    fcntl()函数原型如下.

    1. #include
    2. #include
    3. int fcntl(int fd, int cmd, ... /* arg */ );

    传入的cmd的值不同, 后面追加的参数也不相同.
    fcntl函数有5种功能:
     

    复制一个现有的描述符(cmd=F_DUPFD).
    获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
    获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
    获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
    获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
    我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞

    实现函数SetNoBlock
    基于fcntl, 我们实现一个SetNoBlock函数, 将文件描述符设置为非阻塞.

    1. void SetNoBlock(int fd) {
    2. int fl = fcntl(fd, F_GETFL);
    3. if (fl < 0) {
    4. perror("fcntl");
    5. return;
    6. }
    7. fcntl(fd, F_SETFL, fl | O_NONBLOCK);
    8. }

    使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).
    然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数.

    4.非阻塞代码编写

    说明:用户向缓冲区输入数据,然后将数据读出来打印到显示器,显示器本质上也是一个文件,对应的文件描述符为0,此时将0号文件描述符对应的文件设置为非阻塞,当有数据的时候读取数据,没有数据的时候可以处理其它的业务,而不是阻塞式等待。

    main.cc

    1. #include"util.hpp"
    2. #include
    3. #include
    4. using func_t = std::function<void()>;
    5. #define INIT(v) do {\
    6. v.push_back(PrintLog);\
    7. v.push_back(Download);\
    8. }while(0)
    9. #define callback(cal) do{\
    10. for(auto& e: cal) e();\
    11. }while(0);
    12. int main()
    13. {
    14. std::vector<func_t> cbs;
    15. INIT(cbs);
    16. setNoBlock(0);
    17. while(true) {
    18. char buf[1024];
    19. printf(">>> ");
    20. fflush(stdout);
    21. int ret = read(0,buf,sizeof(buf)-1);
    22. if(ret == 0) {
    23. std::cout<< "read end" << std::endl;
    24. break;
    25. }
    26. else if(ret > 0){
    27. buf[ret-1] = 0;
    28. std::cout << "echo# " << buf << std::endl;
    29. }
    30. else {
    31. //不输入的时候,底层没有数据,不算错误,只不过是以错误的形式返回了
    32. //如何区分是真的错了还是没有数据
    33. //EAGAIN 和 EWOULDBLOCK 都表示没有数据
    34. if(errno == EAGAIN || errno == EWOULDBLOCK) {
    35. std::cout << "没有数据" << std::endl;
    36. callback(cbs);
    37. }
    38. else if(errno == EINTR) continue;
    39. else {
    40. //真的错了
    41. std::cout << ret << "errno: "<< strerror(errno) << std::endl;
    42. break;
    43. }
    44. }
    45. sleep(1);
    46. }
    47. return 0;
    48. }

    util.hpp:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void setNoBlock(int fd) {
    7. int f1 = fcntl(fd,F_GETFL);
    8. if(f1 < 0) std::cerr<< "fcntl fail: " << strerror(errno) << std::endl;
    9. fcntl(fd,F_SETFL,f1 | O_NONBLOCK);
    10. }
    11. void PrintLog() {
    12. std::cout << "this is a LOG" << std::endl;
    13. }
    14. void Download() {
    15. std::cout << "this is a Download" << std::endl;
    16. }

    运行截图:

    总结 

            相信看完这篇文章之后,你一定可以理解要想实现高效的IO,必须要减少等的时间,如何减少等的时间呢?关于这个话题,一般采用IO多路转接的方案,IO多路转接包含select模型,poll模型,epoll模型,关于这三种模型,在后续的文章中为大家一一介绍,感谢大家的阅读,今天我们介绍的内容就结束了。

  • 相关阅读:
    【C语言】库宏offsetof
    【TCP套接字编程,UDP套接字编程】
    618快到了送上自制前端小项目(html css js)
    15 轮转数组
    解决vulhub漏洞环境下载慢卡死问题即解决docker-valhub漏洞环境下载慢的问题
    Linux shell编程学习笔记5:变量命名规则、变量类型、使用变量时要注意的事项
    chrome浏览器关闭百度热搜——AdBlock插件
    深度学习之 10 卷积神经网络2
    量化交易延迟会发生在哪些环节?
    Flink状态编程(二)
  • 原文地址:https://blog.csdn.net/qq_65307907/article/details/132323483