欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

马哥 43_01 _IO复用 有大用

MySQL复制,MySQL-Proxy

MySQL渐渐封闭,要关注MariaDB,PerconaDB

PerconaDB是在MySQL基础上的构建的性能优化的非常强的一个版本

PerconaDB在5.6以后很可能转向MariaDB,不再以MySQL为蓝本了,

MariaDB两年前的全球开发者共十几,二十几个人,现在已有200多人了,中国大陆有六七个了,MariaDB里面没有使用InnoDB,因为InnoDB是属于Innobase公司的,Innobase在2008年时就被Oracle收购了,Oracle如果想收紧口袋的话,MariaDB再让自己陷入别人的控制之下, 不太明智的,MariaDB也支持好多存储引擎,有些也的确支持事务的,但是不如InnoDB成熟,未来三五年或三两年之内,支持事务的引擎会成熟起来的,,国内外企业不再使用MySQL,使用MariaDB的越来越多了

MySQL的高可用(MySQL HA),借助于drbd,共享的iscsi存储,san存储等等,

Web:Nginx ,LNMP,Memcached,haproxy,tomcat,varnish


如何使用Memcached为MySQL提供缓存,缓存本身不是Memcached提供的,而是前端应用程序提供的,如何使用Memcached为PHP缓存session信息,

要做一个varnish集群,负载均衡集群,应该怎么去做,lvs的,或nginx,或haproxy的,都可以实现 varnish的缓存集群



nginx官方站点

http://nginx.org/

image.png

2013-05-13的 1.2.9版本,

2013-05-07的 1.4.1版本,

最新的分支是1.4系列,1.2系列的仍然处于维护当中,


http://nginx.org/en/docs/  文档介绍

image.png

http://wiki.nginx.org/EmbeddedPerlImageResize/   wiki站点

https://www.nginx.com/resources/wiki/ 


   

下图的网址,我为什么看不到

image.png


nginx是 

1)轻量级高性的HTTP服务器,

2)又是高性能的 reverse proxy (反向代理)服务器,,能反向代理两种协议

            http

            mail(smtp,pop3,imap之类的协议)

任何一个代理服务器(比如Mysql Proxy)必须要能够精确理解其所代理的协议或操作,(Mysql Proxy能够理解MySQL的各种查询语句,能够实现读写分离,)

nginx的代理功能(代理http,代理mail)比HTTP服务器功能更拓展,早期就是做反向代理的,

某个俄罗斯人2002年开始开发,2004年第一版公开发布,目前12.18%的市场份额,2千2百多万虚拟主机或服务器用的是nginx,nginx高性能,高稳定性,丰富的特性,简单的配置,低资源消耗,,nginx是一种 web 服务器, 解决 C10k (connection 10k)问题(当一个服务器的并发连接达到10k时,会带来众多问题,)




https://www.netcraft.com/  统计全球web服务器的占有状况,

下图马哥的,我为什么看不到

image.png

image.png

上图,apache最多,然后iis(中小企业大多数,配置简单),nginx一直上升,


nginx比较新,设计上,充分考虑了现代网络的需求以及工作状况,


http://www.kegel.com/c10k.html

image.png


 如下图,当用户请求进来的时候,我们如何通过本地的进程或线程响应,怎么让工作在一个套接字上的服务,响应众多的连接请求,不能使用阻塞了,(apache使用了prefork模型,)

image.png

如下图,互联网早期时,如果访问的人相当少,可以工作在阻塞模型当中,第二个用户的话要等待

image.png


如下图图一,apache使用了prefork模型

有个控制进程(主要目的是管理子进程的,生成新子进程,销毁空闲子进程等),当有外部请求时,生成子进程响应用户,子进程专供用户使用

(每个用户每个进程响应,进程是非常重量级的资源实体,自身开销大,每个进程就是需要在cpu调度的单位,需要分配一定的内存资源,分配cpu时间片,内核还要不停的去调度执行,,进程多,会有大量进程切换)(假如仅一个cpu,某一时刻,能运行的进程只有一个,所以要切换)(比如说每个进程只占用cpu 5毫秒的时间)(内核负责处理进程切换的)如下图图二,

图一

image.png

如下图图二 第一个进程执行5毫秒赶紧退出,(内核负责处理进程切换的),,,我们所有的系统的控制进程的切换在内核中进行,某一时刻,内核在cpu上运行,内核自己内部有程序,(比如运行进程调度程序),观测每个进程的属性,发现下次该轮到3号进程了,于是唤醒3号进程,载入到cpu的各种寄存器里面来,把cpu的运行指针指向3号进程的地址空间,然后内核退出,内核转为睡眠状态,然后3号进程运行,3号进程运行5毫秒结束以后,内核重新在cpu上运行,将3号进程转入睡眠状态,然后内核再进行一般的算法,挑4号进程进来,这就是进程切换 (每一个进程切换会占用cpu时间,切换频繁的话,大量时间耗在进程的切换上了,进程切换本身不有助于解决问题)(在内核空间中所消耗的时间(工作在system当中的时间)通常都是额外的内核开销,)

多进程模型有这样一个缺陷,

图二

image.png


如下图,多进程模式下,每一个进程都是独立运行的单位,它们很可能请求同一个资源,比如首页,,,用户进程不能访问硬盘,通过系统调用转入内核模式,此用户进程转入睡眠状态,(它不是睡眠,是处于阻塞状态了)(它产生了IO调用,)(用户进程等待内核进行读取硬盘,即转为睡眠状态,即处于阻塞状态,这叫不可中断睡眠,,uninterruptable,,,把它唤醒还是没用的,还是不能响应用户请求的),,,因为这是IO产生的阻塞,所以它要等待IO的完成,这在cpu的时间上,叫做wait,,,,此时内核帮助去硬盘取数据,数据取回来后,先放到内核自己的内存空间(buffer或cache),取完数据后,把数据从内核空间复制到进程空间,此时进程才能访问的


image.png

如下图,数据如何从硬盘加载到内存?

内存空间是分页的,

buffer或cache,有些地方不分页,是堆内存,

数据从硬盘加载进来,很可能不是一下子全加载的,一下子只能读取一个页面的数据,

在内核当中,内存空间不分页,????????

假设每个页框的大小是4k,一个IO应该能够读一个页面即4k的大小,磁盘上的块(有的是1k,2k,4k)(如果是1k的话,读4个块,),,,,读进来,填满这样一个页面,,再读进来,填满第二个页面,,,,数据读完了,加载结束了,然后开始复制到用户进程空间

当时有时候,访问一个文件的时候,可能不用访问文件中的所有数据,可能处理数据是其它的方式,马哥不详述

image.png

如下图,如果一个文件需要占据10个内存页,假如磁盘块每2k一个块,那么需要20个块,每次需要读哪个块,读完以后放到哪个内存当中,需要cpu的参与.(cpu必须要在内核的内存中分配出来一个空闲空间,然后把数据加载到内核的内存中)(内核空间中只是为了缓冲的,)(最终数据要复制到用户空间对应的内存中)(我们如何选择一个空闲内存,从哪里分配,要从磁盘上哪个地方去读取)(内核操作的是文件系统,文件系统最终对应的数据一定是磁盘块,对应的是哪个磁盘块,是由文件系统驱动程序去管理的,驱动程序运行是内核的功能,驱动程序运行也需要cpu,这些都需要用到内核的参与)

为了加载一个文件,内核空间很繁忙,为了尽可能的降低内核对cpu的占用,让我们当前主机上的cpu尽可能运行其它的用户空间的程序,现代很多硬件都具有了DMA(直接内存访问 Directory Memory Access)的机制image.png

如下图,有了DMA之后,假如用户空间需要10个页面,我们的内核在自己缓冲区里面,找一段连续的缓冲空间,有个起始地址,把起始地址给DMA芯片,(DMA是硬盘或主板上的一个芯片,它是一个控制芯片,有cpu的某种功能,有访问内存的能力,有复制数据的能力,有数据传输占据系统总线的能力,它有控制系统总线,数据总线,地址总线?????的能力),,cpu决定起始地址,并决定读哪些数据之后,把总线控制权总给了DMA芯片,由DMA指挥着将数据从磁盘上加载至内存(它占据了数据总线),DMA发送控制指令,读第几个磁盘块(它还占据控制总线),,,,,然后再占据数据总线,读到哪个内存空间里面去,,,,这些过程由DMA芯片执行,,,,

此时,cpu干什么(有些用户进程已经拿到数据了,用户进程与网卡进行交互并响应给用户的时候,不需要占据数据总线了,用户进程只需要通过PCI总线与网卡设备联系就行了),,,cpu把已经准备好数据的进程跟用户建立交互了,cpu不需传数据了,

DMA该传数据传数据,

DMA控制着把所有的数据都读进内存了,DMA不能控制用户空间的进程去执行给用户的请求进行响应,,,所以当数据加载完成了,DMA要产生一个中断,强行告诉cpu(有个信号发给cpu),数据传输已经完成了,,,,而cpu正在执行用户空间的进程,此时cpu中断执行用户空间的进程,用户空间的进程转入睡眠状态,让内核去执行cpu,将DMA已经加载进内核空间的数据复制给一个用户进程,这个过程很复杂

所以开发一个操作系统内核是非常非常非常非常困难的,中国连个操作系统都没有,这背后涉及到许许多多的复杂的仲裁机制或解决方案.

image.png


如下图,多个进程访问同一个文件首页,第一个进程IO,加载文件,内核内存,进程内存,,,,,,,,第二个进程IO,加载文件,内核内存,进程内存,,,,,当然内核有加速机制,第一个进程读完之后,它会在内核空间中缓存下来了数据,第二个进程再访问的时候,它就是从内核空间复制给第二个进程的内存空间,

image.png


如下图,假设这四个用户空间的进程访问的都是同一个主页,主页有2M的大小,在用户空间的内存里面,它要占据8M的大小,,,,,,但是里面的内容是一样的(事实上我们只需要2M就能解决问题了),,,,,这是多进程模型的巨大的缺陷,,

image.png


单进程: 阻塞,

多进程: 每个进程响应一个请求,

        进程量大,进程切换次数多(切换本身需要大量的资源),1万个请求,c10k进来了,

        每个进程的地址空间是独立,很多空间是重复的数据,所以内存利用效率极低

线程:  (thread 线程),在linux上,被称为轻量级进程 (Light Weight Process ,,即LWP ),是进程内部的子运行单位,linux并不支持原生态的线程,(windows支持,sorias也支持),linux把所有线程当作进程来对待,只不过在管理上略有区别,在linux上实现真正线程功能的,需要借助于glibc当中所提供的线程库来完成线程的创建,撤销等各种管理功能,,,而目前linux上,提供线程管理功能的线程库有很多种.有内核自带的,也有红帽专门提供的星线程库,,,不同的线程库对线程管理所需要消耗的资源是不一样的,可以搜索一下"linux线程库有哪些类型" "如何切换当前系统上所使用的线程库"

        每个线程响应一个请求:

                线程依然切换: (因为线程也是每一个独立的执行实体),线程切换量级比较轻(属于轻量级的),很多缓冲下来的数据,只要是同一个进程的,不要重新置换了,

                同一个进程的线程可以共享进程的诸多资源,比如打开的文件,传送的信号等,多用户访问同一个文件的时候,在性能上比多进程模型好很多.......

                对内存的需求较之进程略有下降.................既然很多资源可以共享,那么线程本身对于内存的占用量是变小了,


 线程依然需要切换,不管如何,进行c10k个线程请求,就需要10k个线程来响应,,,依然需要切换,依然可能导致资源占用率很高的,更重要的是,虽然线程在同一个进程内部,虽然类似的它可以并行执行,但是 cpu颗数很少,只有一个cpu,线程的优势几乎发挥不出来,如果有多颗cpu,就有好处了,每一个线程可以并行的在cpu上去执行,第一个线程在第一个cpu上执行,第二个线程在第二个cpu上执行,,,,,,,,程序使用了并行编程机制,(在一个进程内部,或者在一个线程内部),,,它可以有多个执行流并行执行的话,可以实现更好的去分配系统资源这样一个效果,,这样子的话切换次数就变少了,,我有10颗cpu,运行一万个进程,,我们每个cpu只需要1千个切换就可以了,,,,,除了web进程之外,还有其它进程,并不是只在web进程之间切换, 我们来了10k个连接,并不是都是一个进程内部进行响应的(一个进程内部管理10k个线程,不可思议,忙不过来,为了解决这些线程之间资源争用,就很麻烦)(虽然资源是共享的,但是其中有一个线程打算要写这个文件,另一个线程要读这个文件,必须要等待人家写完成,,,,所以资源争用依然存在,,,读是共享的,但是读写之间不能共享)如下图,,,,,,,第一个进程正在写,第二个进程要来读,第一个进程恰好正在cpu上运行,第二个进程不能读了,所以进程切换(第二个进程读不了,要等一会儿怎么等?两种等待方式:忙等,闲等)(忙等:第二个进程进来一看,别人正在写,自己读不到,然后切换出去刚切换出去第一个进程写完了,数据准备好了,,,,所以白切换了一次,,那就多等一会儿,,,,cpu是不允许你空闲的,,cpu时间频率一直在那儿,,,只要cpu工作在一定的频率下,,无论有没有程序执行,cpu频率一直在那儿,往前走着呢,,,无论你用不用,只要在供电,cpu一直在转,无非是计算能力流失了而已,,,,,如果没有指令执行的话,第二个进程不能站在cpu上飘着,cpu运行个指令,每隔1毫秒,就过来看一下第二个进程,,这就叫忙等,,,,,,一直处于过来看看这个状态,而不退出cpu,始终占用着,我的时间没耗完,什么事也干不了,第二个进程也不走)(闲等:时间没耗完,但是时间干不了,于是就自动退出了,自愿退出,让给其它进程执行,,把进程切换出去,就叫闲等)

image.png

如下图,

忙等:自旋锁, spin lock

闲等:

当我们同一个进程内部,资源争用很严重的,所以进程为了解决线程这些资源争用的问题,会很忙的,,更何况,在同一个进程内部,如果线程过多,而且导致大量线程切换的时候,会带来另外一个问题,线程抖动

线程抖动:快速切换时,会带来线程抖动(线程切换的过快,一个线程啥事也没干,就切换出去了)

所以,过来10k个请求,不能用一个进程上的所有线程来响应,,所以有了另外一个模型,多进程,多线程模型

image.png


多进程,多线程

    n个进程,每个进程提供一部分线程,比如一个进程100个线程,至少在同一个进程内部,线程资源的争用的情况不会太严重,,,,,多进程,多线程就是各个进程在cpu上切换,但是同一个进程内部线程也需要切换,但是每一个进程都是自我管理它内部线程了,,线程依然需要切换,,,,,,,,,,,,,,多进程多线程至少可以带来另外一个好处,如下图,为了能够提高cpu利用率,让它的切换更属于轻量级,,一个进程内部有多个线程,而有我们有多个进程,假如8颗cpu,只留一颗cpu出来,我们的操作系统有很多进程,这个cpu运行内核,运行其它各种进程,其它7颗独立出来了, 空闲出来了,而后,这7个cpu不给它分配任何进程(开机时就隔离出来了),,web服务器启动起来以后(启动7个web进程), 把每一个进程特定的绑定在某一个cpu上,在这些cpu上不需要进行进程切换了,,,,但是一个进程内部有线程,线程是需要切换的,,,这样子的话,会提高系统使用效率的,,,,所以在一个cpu上,只切换线程,很多线程的资源是共享的,这样子速度会快很多,,,,,,,当然我们的操作系统提供的这种能力,要真正达到这种效果,要完成这种功能,得自己手动去进行,,,得自己隔离cpu,得自己手动绑定web进程到cpu上,,,,就算是进程不需要在cpu上切换了,而同一个进程内部的线程依然是需要切换的,,,,,,,所以来10k个请求,就算你使用线程,它响应起来,仍然是有可能是很吃力的,

image.png

所以就有了单进程(或者叫单线程)(同时为进程或线程,是一个概念)  单进程多请求或者叫单线程多请求

叫多线程:n个请求,

        就是一个线程,响应多个请求,

早先一个进程一个请求,或一个线程一个请求,主要是每个请求,它可能请求的资源不一样,资源请求必然涉及到磁盘IO,在完成磁盘IO的时候,这个进程需要等待,等待磁盘IO完成以后,去独立的响应请求的,,,,,,,

如下图,当一个线程请求多个请求的时候,意味一个线程处理多个IO了,,,,,,,每一个请求过来,大家并发,都在一个线程上运行,假设第一个线程请求一个文件,这个线程要负责系统调用,(由内核完成系统调用,从磁盘上去加载文件,文件加载过来以后,怎么知道是哪个请求的呢???当然可以给每一个请求内部有一个标识,但是,当一个磁盘IO完成的时候,我怎么知道这是哪一个请求的?我怎么知道让这个连接又处于活动状态呢?)(一个连接进来,现在为了给它响应资源,那么这个连接要处于非活动状态了(或等待状态)(怎么让处于等待状态的连接给它唤醒))(一个线程一个请求,只需要唤醒那个线程就可以了)(一个进程一个请求,只需要唤醒那个进程就可以了)(现在的问题是一个线程,N个请求,怎么唤醒那个请求???)(只要请求了资源,现在我们发现这个线程第一个请求的资源正在IO,所以这个请求处于等待状态了,,于是处理第二个请求,接着处理第三个,当处理第三个的时候,这个IO完成了,要唤醒第一个请求,)(怎么唤醒第一个请求?连接在外面????在内核里面,,,连接是网络连接,连接本身是在网卡上进来的,跟TCP/IP相关的,TCP/IP协议栈相关的内容都是要由内核来处理,)很显然,这个请求必须要能连接我们的内核,把这个请求重新激活才行,

image.png

如下图,当来自于同一个线程内部,来自于多个IO的时候,每个IO要交给内核去执行,当内核准备好一个IO之后,它要通知给线程,它怎么通知线程是哪个IO完成了,(我们IO有两个,网络IO和磁盘IO,)(我们以磁盘IO来理解),,,,,,通知给线程以后,怎么激活前端连接呢.再响应给前端连接呢?,,,,,,,,这个IO可以两段的(前端IO????如何完成????网络IO如何完成?????当请求量很大的时候,如何有效解决)

image.png

 不光是前端IO阻塞,磁盘IO也有可能阻塞的,

如下图,假设有第一个请求,它要请求一个磁盘IO,于是我们的线程就来内核中系统调用,加载磁盘文件去了,加载的同时,线程干什么?

image.png


如下图,我们事实上可能有多进程,每个进程响应一个用户请求,先不管前端的网络IO是怎么完成的,看看磁盘IO怎么完成?每一个进程请求一个IO,当IO没有完成的时候,这个进程就阻塞了,它就干不成别的事了,,,,,,同样的道理,第二个进程IO的时候,它也有空闲,空闲时也干不成别的事了,干不成别的事,它就不用占据cpu了,此时cpu就可以切换到其它进程去运行了,当多个进程同时发起IO请求的时候,我们的内核一旦准备好一个IO,(假设内核为第二个IO准备好了,它怎么通知第二个进程,告诉你已经准备好了,或者说第二个进程怎么知道自己的IO准备好了,,,,,进程可并不是说我为每一个IO追踪的很好,你可以随时跟我交互的),,,我现在管理的进程很多很多,每一个进程都要产生一个独立的IO,内核中需要准备N个数据结构,我最多告诉你数据已经准备好了,但是我告诉哪个进程呢?就算我告诉第二个进程,你的IO好了,而第二个进程怎么知道自己的IO好了呢?所以为了让大家知道每?某一个进程是不是都好了,而且让大家知道我是通知的第二个进程,那该怎么办?简单来讲,我们的内核需要输出一个数据结构(这个数据结构,假设它是一排,一排灯,来一个进程,放一个灯,这个灯亮了,就是这个IO好了,)于是每隔一段时间,我们内核就要(假设准备好某一个IO了,它就要把每一个灯泡挨个处理一遍,,第一个灯泡,不能让它亮,第二个灯泡,不能让它亮,第三个灯泡,让它亮起来,通知到这儿了,然后上面的进程就知道谁的好了),,,,,,,我们内核每一次挨个的把整个数据结构向用户空间输出一遍,,,,不输出,这些进程是无法理解的,,,,像这种方式,每一次都挨个的扫描一下每一个IO的文件描述符,而后将它从内核空间输出给用户空间的这种方式,我们称为select模型,最多支持1024个(最多支持1024个灯泡,再多的话,没有那么多灯泡可用了,这么多进程,这么多IO同时运行,我处理不了了,所以你只能等着,)1024个请求,在早些年,已经是很大的量了,但对我们c10k的连接显然不理想,,,,,,,,,,,,

所以这就是单进程,每个进程响应一个用户的请求,它所面临的难题和在IO方面必须要解决的问题,所以早期内核中使用select算法,使用了一个系统调用,就叫select,,,,,,这种系统调用,内核每准备好一个IO,它就会扫描一遍这整个数据结构,然后将这数据结构输出给用户空间,哪个线程(哪个进程)看到自己的IO好了,就可以拿数据过去响应了,,,,,,,,,,,,,,,进程模型依然摆脱不了这个问题,只不过在线程模型下,真正负责来进行IO的通常是跟进程相关的,,,,,,,,在线程模型下,由进程来负责IO,所以它减少了进程数量,意味着IO的数量(我们需要输出的文件的量)要小一点,所以它可能能响应多一点的任务请求,,,,但是linux上,线程即进程(线程不过是轻量级的进程),所以在磁盘IO上没有很好的解决方案,,,只不过在前端的响应上,多个线程可以共享资源了,至少能够提高内存使用效率了,,,,,所以线程的好处无非是提高了内存使用效率,共享了一些资源,响应速度得到了提高,但背后的IO机制仍然都是一样的,,,既然IO机制都一样,那么它想要响应更多用户的请求,响应10k个连接,能解决问题吗?不能,,,,,,,,,,,,,,,

image.png


如下面三个图,所以我们提到了一个线程响应多个请求,只有一个线程,内核如何响应多个请求呢?进来了多个用户请求,假如第一个用户请求IO,当它请求IO的时候,进程要等待,要阻塞,它要一阻塞,就切换出去了,,第二个请求不能处理了(假如第二个请求没有请求IO,只是运行php,,难道要把人家阻塞出去?) (所以一个线程响应多个请求,一阻塞,,,阻塞的可不是一个请求,而是所有请求),,,,很显然,当我们请求一个IO的时候,不能阻塞,,,,,,,,所以一个线程,多个请求的时候,当一个线程需要产生一个IO的时候,如果这个IO没有完成,这个线程是不能阻塞的,,所以这个用户请求先在这边空闲着,我去处理别的请求了,(像其它的一个进程一个请求我一阻塞就OK了),,,但是现在我不能阻塞,等内核那边准备好了,怎么通知给线程?(不能阻塞,意味着线程没有等着,)(你在这儿等着,我做好了给你就是了,,,,,你现在没在这儿阻塞,我怎么办??等一会儿 内核怎么通知给线程?内核怎么知道是哪个线程哪个请求? (一个线程响应多个请求,假如说我启动了三个线程,多线程,多请求,)(由于这个线程就j是进程了,也没有没有子线程的概念了),所以一旦不阻塞了,等会儿内核这边准备好了,怎么通知给线程????这个概念此前曾经举过例子,

image.pngimage.png

image.png

如下图去饭店吃饭的例子,叫一碗饭,柜台营业员接待了我,假如就一个营业员,一下子来了10个顾客,要是阻塞模型,第一个用户来一碗面,营业员要告诉后厨的师傅(内核),有人要了一碗面,当内核准备好了,它要告诉营业员,做好了,服务器告诉第一个用户做好了,,,,然后是第二个用户请求,后面的用户要等待,,,,

image.png

如下图,为了的高效率,按排三个营业员,排三个队,一个队上三个人,总共九个客户,,,,每一个营业员的工作量减轻了,后厨大师傅可以一下子开八个火,,,,,,如果工作在阻塞模型下,第一队里面,第一个客户办完了,第二个客户才能上来,(这叫阻塞,只不过是多对多的阻塞),,,,,如果在非阻塞模型下,意味着,第一个客户点完面之后,在一边等待了,第二个用户可以去报饭,第二个用户报完饭之后,第三个用户去报饭了,,,,后厨大师傅可以同时开好多锅,后厨师傅做完某一碗饭之后,如何知道是哪个营业员请求的饭?,,,,,,,,,,,,,,,,,,这个过程是两段式的,第一个用户请求饭了,假设它不阻塞,在旁边等着,那我们的营业员跟后厨报上饭以后,营业员在等着呢?还是可以继续接待第二个用户呢?接待第二个用户没错,不跟后厨联系了,后厨一碗饭做好了,他怎么知道这是谁的饭?他怎么知道哪个营业员接待的顾客呢???? 我们的内核只是告诉你,我们的某个IO完成了,因为每一个用户请求的时候,它是有文件描述符(一个IO请求跟一个文件描述符),它说这个文件描述符已经准备成功,谁请求的,哪一个营业员请求的,内核是不负责管理的,(我们要是所有的事情都负责管理的话,内核要忙死了),,,,你请求进来,我准备成功,准备好了以后,我就放在这儿,放在窗口这儿,什么面好了?谁的面,不知道,,,,三个营业员,你说是谁接待的客户的面?每个营业员接待的顾客里面都有面,怎么办??如果阻塞的话,意味着一个营业员只能接待一个用户的请求,营业员等待后厨做好了,就端给顾客了,然后营业员接待第二个顾客(阻塞的话,这种方案要简单得多),,,,,,,一个营业员可以接待多少顾客,就是刚才所谓的一个线程多个请求了,意味着用户报完饭,营业员交给后厨,然后营业员与后厨失去联系,营业员接待第二个顾客,再跟后厨报完饭,,,当后厨把某一碗饭端上来之后,后厨怎么知道是谁的饭,,更何况的是,营业员怎么知道后厨做好了,(如果有阻塞的话,问题简单,后厨做好了,营业员立马看到了,因为营业员一直处于等待状态,一直盯着,立马能看得见后厨做好了)(如果是非阻塞状态的话,后厨做好了,朝台子上一放就走了,营业员怎么知道是谁的饭,假如说我们的内核使用一种信号机制,不管是哪种机制,反正是通知给我们的用户请求,那解决了它们背后的阻塞和非阻塞问题????)(这个IO工作在非阻塞模型下了,当我们工作在非阻塞模型下以后,再看前端,前端我们现在要接待多个用户了,第一个用户请求以后,它的饭没有做好,告诉它,先去等,做好了之后,再喊它,这种场景下,前端用户是什么模型?)(我要一下饭,等待营业员给我,给我以后,我就退出,这是什么模型?)(我要一下饭,直接把请求丢给营业员,我直接在旁边等着,我去玩了,过一会儿,我再看看做好了没有,这是什么模型?)(当第一个顾客报完饭以后,营业员太忙了,他没有立即报给后厨,营业员觉得还可以接待第二个顾客,第三个顾客,可以汇总后一并报给后厨,,这叫异步通信)(当顾客要了,营业员立即报给后厨,再去接待第二个顾客,这种协议叫同步通信,,来自于前端的直接交给后端了),,,,,,,,,,,异步与阻塞是两码事,异步与非阻塞也是两码事,,,,,,,,,,,不管怎么讲,反正这几个用户都去一边等着了,过一会儿,当饭做好以后,人家后端解决了后端IO的问题,把饭端上来了,怎么让用户过来拿?此时用户坐在一边等了,要有办法告诉用户,饭已经好了,,,后厨不能往台上一端就不管了,可以放一个电子屏幕,告诉用户,你的饭一会儿可能做好,你自己盯着电子屏幕看,所以做好一碗饭,在电子屏幕上通知一遍,,,,,为了让每个用户知道自己亲自做的饭好了没有,电子屏幕上写好,1号的饭没好,2号的饭好了,3号的饭没好,4号的饭好了,,,,,某一个用户来看自己的饭有没有好,都得把整个屏幕看一遍,必须得从上往下看一遍,自己的饭没好,还得等着,,,,过一会儿再看,自己的饭没好,还得等着,,,,这个时候,很麻烦的,,,,,,,,我们可以简化一种机制,只通知好的,不通知没好的,,,(如果你的饭好了,我就直接显示在屏幕上,,如果没好,就不会显示在屏幕上,你得在旁边继续等,,这样子,至少,每一次电子屏幕显示的信息量会少得多,也就是我们内核要处理的机制要小得多)(问题是你通知好了的,要通知几遍?你通知了一下,显示了一遍,显示了之后,你去干别的事了,举个牌,1号好了,2号好了,3号好了,举一遍,下去了,继续处理别的机制,因为新的顾客要来,如果某个顾客没有看到自己的好了,怎么办?没看见?管你呢,反正我已经通知过了,,,,还有一种做法,你没看见,我再通知一遍,,,,,,,,,,,,,,,这一种就叫通知机制,它不需要用户自己始终盯着,不然的话,用户自己始终看着营业员,,,,,,我们的营业员只需要负责通知就行了,这种就使得我们的顾客(用户)可以工作在异步模型下,,用户不用一直等着,该干别的事,就干别的事,由我们的营业员负责通知,像这种通知,它通知的时候,可以只通知已经完成了的,也可以已完成的未完成的都通知一遍,,可以通知一次(不管你有没有取到饭),我也可以多通知几次,,,,,,,,,,,,,像这种解决了多个用户同时过来请求,又都能够让每一个用户知道自己的请求是否准备就绪的状况,我们就称为多路IO的解决机制)(营业员很忙啊,一方面要接待顾客,一方面要举牌,告诉谁的饭好了)

image.png





多路IO, (IO复用)


 如下图,一个线程响应多个请求,有什么优势,劣势,首先要解决的第一个问题是在磁盘IO中,它不能因为一个磁盘IO没满足,没完成,要阻塞,,是不允许的,,,,,,,,,,所以在文件上,在IO请求上(本机的磁盘IO上),要使用异步IO,不能因为IO要完成,要阻塞我们的线程,,,,,,,,,再看前端,当它响应多个用户请求的时候,既然一个人接待多个用户,那么在某一时刻,如果这个用户的请求尚未得到满足,那么这个时候,那么此时这个用户的请求必须要处于一旁,处于等待状态,,,,当内核为某个用户的请求准备完成之后,前端它必须要实现将某个用户的请求再重新建立起来,并通知给这个用户,你可以过来取数据了,怎么通知给用户,怎么在网络上完成多用户IO的唤醒???在非阻塞模型下,通过内核中的多路IO复用机制来完成,这些多路IO复用机制,(像select),select这种机制,它就是实现,假如说一个线程接入多个用户请求的时候,每个用户的请求反应在本机上,,,,,,其实就是一堆文件描述符,一堆套接字文件,所以当某一个用户的请求准备停当之后,准备完成了,那我们必须要想办法把这个文件描述符激活,并让我们的客户端过来真正的取数据,,,,问题是它怎么通知给我们前端的这个用户,你的数据准备好了,所以它就有了所谓的通知,有了所谓的select等各种方式.


image.png






        不管linux本身对线程支持是什么样的,我们以线程的模式来讲一讲它是如何响应用户请求的


如下图,线程是运行在进程地址空间的子单位,假设一个进程,进程内部可以同时启动多个执行流,(线程也是我们的并发执行流,),,,,,,一个进程地址空间里面通常有(1,代码段区域)(一个进程,一个程序运行要很多指令,指令是要操作数据的,,,程序就是指令+数据)(2,数据区域)(初始化为0的数量,静态变量)(3,堆的空间,动态生成内存空间)(4,栈的空间,整个进程中使用的本地变量,静态变量(或者叫全局变量),(初始化为0的数据,)),,,,,,,在cpu上运行的时候,它运行的是指令,它把指令一条一条的执行,只不过用到数据的时候,到数据区,堆区,或栈区去取数据,
感觉这里,马哥也讲不清楚

image.png

如下图,在堆区中的数据,通常都是打开的文件,

下图不必精确理解,大致理解就可以,,


image.png


如下图,每一个进程的数据都是自我管理的,没有线程的话,意味着执行流的执行指令,只有一条,只有一个流水线,它自上而下,按照我们的控制流程一个一个的执行,如果用到循环的话,它无非就是在局部循环而已,,,

image.png

如下图有线程的话,执行流可以是并发多个执行流,这些执行流共享这些数据区域,,,每个执行流都是一个独立的实体,

image.png


如下图,这些线程就是运行在同一个进程地址空间里面,共享了很多资源的运行的独立实体,所以当某一个请求来的时候,它用一个线程来响应,如果这个线程需要加载文件,跟刚才一样,系统调用,文件加载,而后放入数据区域里面了,,,,,,,,,,如果第二个请求来了,用第二个线程来响应,如果访问的是跟第一个线程同样的文件,那么进程的地址空间已经有了数据,所以从这个地址空间中就能够直接访问文件了,再也不需要重新去加载了,,,,这就是线程的好处

image.png



一个进程到cpu上去执行的时候,一个进程尚未执行完,我们可能要把它切换出去,切换出去之后需要保存线程,切换进来需要恢复线程,(恢复线程指的是 我这里有100个指令,现在执行到第70个了,有个指令指针)(我们的很多指令要处理,是需要数据的,这些数据要被cpu处理,必须要加载到cpu的缓存当中,一级缓存,二级缓存,一级指令缓存,一级数据缓存,二级缓存,cpu的寄存器,,,,这些数都必须要恢复过来的),所以它涉及到大量操作,,,,,,,,,,,,而线程切换的时候,很多数据是在进程内部共享的,所以缓存里面就不要切换了,,,,所以如果是同一个进程的线程间切换,没有问题,,但如果要切换到别的进程去了,这仍然是要涉及到众多资源的切换,,,,






普通分类: