Linux内核(六)阻塞&非阻塞

Linux内核(六)阻塞&非阻塞,第1张

阻塞和非阻塞IO是访问设备的两种模式,驱动程序可以灵活的支持者两种用户空间对设备的访问方式。

如下图所示,在阻塞访问时,不能获取资源的进程将进入休眠,它将会让出CPU,因为阻塞的进程会进入休眠状态,所以必须要有一个动作能唤醒该进程,唤醒进程的地方最大的可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。而非阻塞的进程则不断的尝试,直到可以进行IO。

在Linux驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞队列的唤醒。等待队列以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问,

关于IO会涉及到阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO等几个知识点。知识点虽然不难但平常经常容易搞混,特此Mark下,与君共勉。

阻塞IO情况下,当用户调用 read 后,用户线程会被阻塞,等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后 read 才会返回。可以看到是阻塞的两个部分。

非阻塞IO发出read请求后发现数据没准备好,会继续往下执行,此时应用程序会不断轮询polling内核询问数据是否准备好,当数据没有准备好时,内核立即返回EWOULDBLOCK错误。直到数据被拷贝到应用程序缓冲区,read请求才获取到结果。并且你要注意!这里最后一次 read 调用获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是 内核态的数据拷贝到用户程序的缓存区这个过程

非阻塞情况下无可用数据时,应用程序每次轮询内核看数据是否准备好了也耗费CPU,能否不让它轮询,当内核缓冲区数据准备好了,以事件通知当机制告知应用进程数据准备好了呢?应用进程在没有收到数据准备好的事件通知信号时可以忙写其他的工作。此时 IO多路复用 就派上用场了。

IO多路复用中文比较让人头大,IO多路复用的原文叫 I/O multiplexing,这里的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 发明它的目的是尽量多的提高服务器的吞吐能力。实现一个线程监控多个IO请求,哪个IO有请求就把数据从内核拷贝到进程缓冲区,拷贝期间是阻塞的!现在已经可以通过采用mmap地址映射的方法,达到内存共享效果,避免真复制,提高效率。

select、poll、epoll 都是I/O多路复用的具体的实现。

select是第一版IO复用,提出后暴漏了很多问题。

poll 修复了 select 的很多问题。

但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。

epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:

横轴 Dead connections 是链接数的意思,叫这个名字只是它的测试工具叫deadcon。纵轴是每秒处理请求的数量,可看到epoll每秒处理请求的数量基本不会随着链接变多而下降的。poll 和/dev/poll 就很惨了。但 epoll 有个致命的缺点是只有 linux 支持。

比如平常Nginx为何可以支持4W的QPS是因为它会使用目标平台上面最高效的I/O多路复用模型。

然后你会发现上面的提到过的 *** 作都不是真正的异步,因为两个阶段总要等待会儿!而真正的异步 I/O 是内核数据准备好和数据从内核态拷贝到用户态这两个过程都不用等待。

很庆幸,Linux给我们准备了 aio_read aio_write 函数实现真实的异步,当用户发起aio_read请求后就会自动返回。内核会自动将数据从内核缓冲区拷贝到用户进程空间,应用进程啥都不用管。

我强力推荐C++后端开发免费学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

同步跟异步的区别在于 数据从内核空间拷贝到用户空间是否由用户线程完成 ,这里又分为同步阻塞跟同步非阻塞两种。

我们以同步非阻塞为例,如下可看到,在将数据从内核拷贝到用户空间这一过程,是由用户线程阻塞完成的。

可发现,用户在调用之后会立即返回,由内核完成数据的拷贝工作,并通知用户线程,进行回调。

在Java中,我们使用socket进行网络通信,IO主要有三种模式,主要看 内核支持 哪些。

同步阻塞IO ,每个客户端的Socket连接请求,服务端都会对应有个处理线程与之对应,对于没有分配到处理线程的连接就会被阻塞或者拒绝。相当于是 一个连接一个线程 。

BIO特点

常量:

主类:

服务端监听线程:

服务端处理线程:

客户端:

同步非阻塞IO之NIO :服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时说明读就绪,则调用该socket连接的相应读 *** 作。如果发现某个 Socket端口上有数据可写时说明写就绪,则调用该socket连接的相应写 *** 作。如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高,在进行IO *** 作请求时候再用个线程去处理,是 一个请求一个线程 。Java中使用Selector、Channel、Buffer来实现上述效果。

每个线程中包含一个 Selector 对象,它相当于一个通道管理器,可以实现在一个线程中处理多个通道的目的,减少线程的创建数量。远程连接对应一个channel,数据的读写通过buffer均在同一个 channel 中完成,并且数据的读写是非阻塞的。通道创建后需要注册在 selector 中,同时需要为该通道注册感兴趣事件(客户端连接服务端事件、服务端接收客户端连接事件、读事件、写事件), selector 线程需要采用 轮训 的方式调用 selector 的 select 函数,直到所有注册通道中有兴趣的事件发生,则返回,否则一直阻塞。而后循环处理所有就绪的感兴趣事件。以上步骤解决BIO的两个瓶颈:

下面对以下三个概念做一个简单介绍,Java NIO由以下三个核心部分组成:

channel和buffer有好几种类型。下面是Java NIO中的一些主要channel的实现:

正如你所看到的,这些通道涵盖了UDP和TCP网络IO,以及文件IO。以下是Java NIO里关键的buffer实现:

在微服务阶段,一个请求可能涉及到多个不同服务之间的跨服务器调用,如果你想实现高性能的PRC框架来进行数据传输,那就可以基于Java NIO做个支持长连接、自定义协议、高并发的框架,比如Netty。Netty本身就是一个基于NIO的网络框架, 封装了Java NIO那些复杂的底层细节,给你提供简单好用的抽象概念来编程。比如Dubbo底层就是用的Netty。

AIO是异步非阻塞IO,相比NIO更进一步,进程读取数据时只负责发送跟接收指令,数据的准备工作完全由 *** 作系统来处理。

推荐一个零声教育C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 立即学习

原文:阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端

From : https://blog.csdn.net/qq_39382769/article/details/960753461.同一个线程内部,指令按照先后顺序执行;但不同线程之间的指令很难说清楚是哪一个先执行,在并发情况下,指令执行的先后顺序由内核决定。 如果运行的结果依赖于不同线程执行的先后的话,那么就会形成竞争条件,在这样的情况下,计算的结果很难预知,所以应该尽量避免竞争条件的形成。 2.最常见的解决竞争条件的方法是:将原先分离的两个指令构成一个不可分割的原子 *** 作,而其他任务不能插入到原子 *** 作中! 3.对多线程来说,同步指的是在一定时间内只允许某一个线程访问某个资源,而在此时间内,不允许其他线程访问该资源! 互斥锁 条件变量 读写锁 信号量 一种特殊的全局变量,拥有lock和unlock两种状态。 unlock的互斥锁可以由某个线程获得,一旦获得,这个互斥锁会锁上变成lock状态,此后只有该线程由权力打开该锁,其他线程想要获得互斥锁,必须得到互斥锁再次被打开之后。 1.互斥锁的初始化, 分为静态初始化和动态初始化. 2.互斥锁的相关属性及分类 (1) attr表示互斥锁的属性 (2) pshared表示互斥锁的共享属性,由两种取值: 1)PTHREAD_PROCESS_PRIVATE:锁只能用于一个进程内部的两个线程进行互斥(默认情况) 2)PTHREAD_PROCESS_SHARED:锁可用于两个不同进程中的线程进行互斥,使用时还需要在进程共享内存中分配互斥锁,然后为该互斥锁指定属性就可以了。 互斥锁存在缺点: (1)某个线程正在等待共享数据内某个条件出现。 (2)重复对数据对象加锁和解锁(轮询),但是这样轮询非常耗费时间和资源,而且效率非常低,所以互斥锁不太适合这种情况。 当线程在等待满足某些条件时,使线程进入睡眠状态;一旦条件满足,就换线因等待满足特定条件而睡眠的线程。 程序的效率无疑会大大提高。 1)创建 静态方式:pthread_cond_t cond PTHREAD_COND_INITIALIZER 动态方式:int pthread_cond_init(&cond,NULL) Linux thread 实现的条件变量不支持属性,所以NULL(cond_attr参数) 2)注销 int pthread_cond_destory(&cond) 只有没有线程在该条件变量上,该条件变量才能注销,否则返回EBUSY 因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程!(请参考条件变量的底层实现) 3)等待 条件等待:int pthread_cond_wait(&cond,&mutex) 计时等待:int pthread_cond_timewait(&cond,&mutex,time) 1.其中计时等待如果在给定时刻前条件没有被满足,则返回ETIMEOUT,结束等待 2.无论那种等待方式,都必须有一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait形成竞争条件! 3.在调用pthread_cond_wait前必须由本线程加锁 4)激发 激发一个等待线程:pthread_cond_signal(&cond) 激发所有等待线程:pthread_cond_broadcast(&cond) 重要的是,pthread_cond_signal不会存在惊群效应,也就是是它最多给一个等待线程发信号,不会给所有线程发信号唤醒,然后要求他们自己去争抢资源! pthread_cond_broadcast() 唤醒所有正在pthread_cond_wait()的同一个条件变量的线程。注意:如果等待的多个现场不使用同一个锁,被唤醒的多个线程执行是并发的。pthread_cond_broadcast &pthread_cond_signal1.读写锁比互斥锁更加具有适用性和并行性 2.读写锁最适用于对数据结构的读 *** 作读 *** 作次数多余写 *** 作次数的场合! 3.锁处于读模式时可以线程共享,而锁处于写模式时只能独占,所以读写锁又叫做共享-独占锁。 4.读写锁有两种策略:强读同步和强写同步 强读同步: 总是给读者更高的优先权,只要写者没有进行写 *** 作,读者就可以获得访问权限 强写同步: 总是给写者更高的优先权,读者只能等到所有正在等待或者执行的写者完成后才能进行读 1)初始化的销毁读写锁 静态初始化:pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER 动态初始化:int pthread_rwlock_init(rwlock,NULL),NULL代表读写锁采用默认属性 销毁读写锁:int pthread_rwlock_destory(rwlock) 在释放某个读写锁的资源之前,需要先通过pthread_rwlock_destory函数对读写锁进行清理。释放由pthread_rwlock_init函数分配的资源 如果你想要读写锁使用非默认属性,则attr不能为NULL,得给attr赋值 int pthread_rwlockattr_init(attr),给attr初始化 int pthread_rwlockattr_destory(attr),销毁attr 2)以写的方式获取锁,以读的方式获取锁,释放读写锁 int pthread_rwlock_rdlock(rwlock),以读的方式获取锁 int pthread_rwlock_wrlock(rwlock),以写的方式获取锁 int pthread_rwlock_unlock(rwlock),释放锁 上面两个获取锁的方式都是阻塞的函数,也就是说获取不到锁的话,调用线程不是立即返回,而是阻塞执行,在需要进行写 *** 作的时候,这种阻塞式获取锁的方式是非常不好的,你想一下,我需要进行写 *** 作,不但没有获取到锁,我还一直在这里等待,大大拖累效率 所以我们应该采用非阻塞的方式获取锁: int pthread_rwlock_tryrdlock(rwlock) int pthread_rwlock_trywrlock(rwlock) 互斥锁只允许一个线程进入临界区,而信号量允许多个线程进入临界区。 1)信号量初始化 int sem_init(&sem,pshared, v) pshared为0,表示这个信号量是当前进程的局部信号量。 pshared为1,表示这个信号量可以在多个进程之间共享。 v为信号量的初始值。 返回值: 成功:0,失败:-1 2)信号量值的加减 int sem_wait(&sem):以原子 *** 作的方式将信号量的值减去1 int sem_post(&sem):以原子 *** 作的方式将信号量的值加上1 3)对信号量进行清理 int sem_destory(&sem)


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/8646594.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-19
下一篇2023-04-19

发表评论

登录后才能评论

评论列表(0条)

    保存