linux C 并发编程概念梳理

要讲清楚 IO 这坨东西. 将按如下章节说明。

1. user space & kernel space

2. multiplexing (select , epoll,AIO[reactor & proactor ] )

3. 各种 IO 的名称

4. 结语

 

user space & kernel space

这两跟 IO 有很密切的关系。所以放在最前面说明, 中文翻译为用户空间与内核空间,啥意思呢?

内核空间:系统管理的内存。用户可以通过 system call 操作内核空间。

用户空间:用户自己写的代码管理的内存。

你把 linux 想像成一个超大的类,它暴露了 api 给你操作它的数据。但你不能直接修改它的数据。

而对 IO 的操作一般为分两步,比方说 Input。

1. 内核将输入放入kernel space。

2. 用户从kernel space。 获得输入数据到 user space。

 

当然,除非你有非常特殊的原因,你也可以使用 mmap。直接操作 kernel space。

 

 

multiplexing

multiplexing 翻成中文技术术语叫多路复用。plex 是堆叠的意思。在 IO 相关的文章里,很常见到。

维基这张图很能说明 multiplexing 这个概念

 

让 1 个 socket 能够同时为多个 socket 服务,可以理解为 socket 具有 multiplexing 的能力。

怎么实现呢?解决的方法有很多,比如:

1. 你可以为每一个连接开一个线程。但缺点是连接一多,内存压力太大。

2. 不用线程,也可以在非阻塞的 socket 上一直轮询,但缺点是连接一多,cpu 压力太大。

上面两种方法,在不改 linux kernel 的情况下都能达到 multiplexing 目的。比如经典的 tomcat 在很早前就是用 thread 去实现 multiplexing。

而什么可以解决以上的提到的缺点,我们看看 select

 

select

select 其实是一个函数的名字。但为交流方便,它现在更像是一种 IO 术语了。

select 在 application 与 kernel 之间放了一块空间 。

1. application 将 IO 设备写到这,然后阻塞。 IO 设备的状态也会被清空。

2. kernel 也会在某个 IO 设备可读可写,或出错时,在这块空间上扫一遍,看有没有相同的 IO 设备。有的话将事件写上,并给一个 syscall 让 application 等的这个线程醒来。

3. application醒了也需要再扫一遍这块空间,看哪个 IO 设备有新事件了。

4. 如此循环。。

所以,两边都瞎.都要扫描。

select 最多 FD_SETSIZE 个 fd(fd 是一个数字,当它是一个 key,关联着对应的 value,通常是一个 file). FD_SETSIZE 是一个编译器的常量,是在 glibc/bits/typesizes.h 里定义的,描述了能放多少 个 fd 到 fd_set。在 32位下,FD_SETSIZE 通常是 1024. 我们看它的这块 buffer 的数据结构:

 

struct fd_set {
    int count;
    struct fd_record {
        int fd;
        bool check;
    } fds[FD_SETSIZE];
};

 

看一下 c 里的接口方法:

# nfds: 你要监控的最大的 fd 号。作用是扫描时,可以确定最大值。 从0 到 ndfs,能少则少点。。
# readfds writefds exceptfds:  分别为需要监控的 fd们(读,写,异常)。
# timeout: select 为阻塞函数,timeout可指定阻塞超时时间。
# 返回值为发生事件的 fd值。
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

 

select 缺点很明显:

1. 连接数量支持有限.

2. 连接越多,性能越差.性能为 O(n).

3. 而且每次都得重置这块空间.

4. 需要用户自己读数据。

而 poll 跟 select 很像,解决了 select 的第 1 个缺点(连接数量限制). 其他两个缺点依旧. select/poll 的性能是 O(N),能不能到 O(1)呢?

 

epoll

要规避 select 的第 2 个缺点,就差一个通知了!某个 IO 设备可读可写或出问题,kernel 你直接通知 application 是哪个 IO 设备不就好了!而且顺带着能优化第 3 个缺点。

 

AIO

IO 发展滚滚向前。AIO 出现了。 名字酷炫,异步 IO。

因为前面说的 select / poll / epoll 都有一个共同的缺点。

需要用户自己读数据。

而 AIO 可以看起来像是要解决这个问题。

为什么这么说。

现在的 AIO 看起来像是内核的实现,其实不然,它是在 glibc 里实现的, 内核的版本貌似还在开发中。

虽然glibc 并不属于内核,但做为普通用户来说,也只是用 glibc 的接口而已。

AIO 的实现可以有很多种,可以使用信号,可以通过线程,也可以通过回调。

 

讲 AIO 不能不讲一下 reactor & proactor pattern

reactor & proactor pattern

reactor 和 proactor 它们都是 multiplexing 的架构模型。 意思也就是,要实现 multiplexing 这个功能。有哪种架构。

它们的最大的区别,在于系统是否利用了 AIO。

在上面我们写微缩版的 EventLoop, 就是基于 select 实现的 reactor 模型。

所以 AIO 到底带来了啥好处?以致于可以多一个 multiplexing 模型命名出来。我们复习一下

前面说的 IO 的操作一般为分两步,比方说 Input:

1. 内核将输入放入kernel space。

2. 用户从kernel space。 获得输入数据到 user space。

第 1 步由内核完成。

第 2 步是要占用用户(application)时间的! 如果第 2 步要用很久,select 实现的 reactor 模型虽然可以支持 multiplexing,但一样很慢。但如果是 AIO 机制,第 2 步会也交给内核。是不是就会快很多!内核帮你把数据都写好了,你直接用就行了。

所以 AIO是什么。简单点讲,就是第 2 步由”内核“完成。而不占用 application 响应时间。

前面也说了 aio 现在是 glibc 实现的。最大区别在于它占用的不是 application 的时间。而并不是整机性能就比 select/ poll/ epoll 强多少。但对于 application 来说,那可是性能强了很多。

关于 reactor & proactor pattern 最初的定义可看看这个 Proactor.pdf,这里面作者就有 ACE 的贡献者,靠谱点。拿几张图。

我们先看一下 reactor client 连接的图:

 

 

 

 

各种 IO 的名称

现在,我们来看一下,因为上面的技术出现的各种花哨的名字!

sync blocking IO

sync non-blocking IO

async blocking IO

async non-blocking IO

我一直觉得这 4 个组合好牵强.对它们的来源有些好奇。查阅网上资料,维基里有解释,然后IBM developerworks也有篇解释,然后 stackoverflow 上也各种解释.百花齐放.然而各自精彩。。这是wiki的

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值