IBM测试,切换线程context的时候,windows比linux快一倍多。进出最快的锁(windows2k的 critical section和linux的pthread_mutex),windows比linux的要快五倍左右。可见多线程这个具体的领域内,linux仍是稍逊windows一点。这应该是情有可原的,毕竟unix家族都是从多进程过来的,而 windows从头就是多线程的。node
(1). 一个进程中全部的线程共享全局变脸和内存linux
(2). 程序逻辑和控制方便算法
(3). 线程方式消耗的总资源比进程要好数据库
(1). 每一个线程和主程序共用地址空间,受限于2G地址空间编程
(2). 一个线程的崩溃可能影响到整个程序的稳定性;windows
(3). 到达必定的线程数程度后,即便再增长CPU也没法提升性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),若是设定线程堆栈为2M,还达不到1500个线程总数; 线程可以提升的总性能有限,并且线程多了以后,线程自己的调度也是一个麻烦事儿,须要消耗较多的CPU 数组
(1). 多个进程相互独立,不影响程序的稳定性,子进程崩溃不要紧缓存
(2). 经过增长cpu能够扩充性能安全
(3). 每一个子进程都有2GB地址空间和相关资源,整体可以到达的性能会比较大服务器
(4). 能够尽可能减小线程加锁/解锁的影响,极大提升性能,就算是线程运行的模块算法效率低也不要紧;
(1). 逻辑控制复杂,须要和主程序交互;
(2). 须要跨进程边界,若是有大数据量传送,就不太好,适合小数据量传送、密集运算
(3). 多进程调度开销比较大
最好是多进程和多线程结合,即根据实际的须要,每一个CPU开启一个子进程,这个子进程开启多线程能够为若干同类型的数据进行处理。固然你也能够利用多线程+多CPU+轮询方式来解决问题……方法和手段是多样的,关键是本身看起来实现方便有可以知足要求,代价也合适
服务器并发:大量客户端同时访问,响应多个客户端。
对于人数少的,数据交互量小的,单进程,单工做线程便可处理。
对于单核cpu,开多工做线程,在线程切换上是很影响效率的,可是对于不一样的程序,单核多线程也可提核服务器的工做效率。
io密集型程序:工做线程处理的回调函数,处于io阻塞状态,好比阻塞状态的send recv,数据库读写操作等,此时的io操作会阻塞线程,此时切换线程处理任务,能够提高服务器效率。io密集型程序因为大部分时间处于阻塞操作,cpu利用率很低,加多线程,可提高cpu利用率,利用多线程屏蔽掉线程阻塞时间。
cpu密集型程序:cpu利用率较高,程序任务并无太多io操作,多线程对其并无多大提高,相反,过多线程的切换,反而会浪费cpu时间,下降服务器效率
进程切换分两步
1.切换页目录以使用新的地址空间。
2.切换内核栈和硬件上下文。
对于linux来讲,线程和进程的最大区别就在于地址空间。对于线程切换,第1步是不须要作的,第2是进程和线程切换都要作的。因此明显是进程切换代价大。
线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,可是进程切换是不一样的。这两种上下文切换的处理都是经过操做系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。另一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中全部已经缓存的内存地址一瞬间都做废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者至关的神马东西会被所有刷新,这将致使内存的访问在一段时间内至关的低效。可是在线程的切换中,不会出现这个问题。
线程安全:要确保函数线程安全,主要须要考虑的是线程之间的共享变量。属于同一进程的不一样线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。所以,对于同一进程的不一样线程来讲,每一个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,若是要保证线程安全,则必须经过加锁的方式。
可重入:概念基本没有比较正式的完整解释,可是它比线程安全要求更严格。根据经验,所谓“重入”,常见的状况是,程序执行到某个函数foo()时,收到信号,因而暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程当中,又偏偏也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时若是foo()可以正确的运行,并且处理完成后,以前暂停的foo()也可以正确运行,则说明它是可重入的
可重入与线程安全并不等同,通常说来,可重入的函数必定是线程安全的,但反过来不必定成立。
这一思路最为直接。可是因为申请进程/线程会占用至关可观的系统资源,同时对于多进程/线程的管理会对系统形成压力,所以这种方案不具有良好的可扩展性。
所以,这一思路在服务器资源尚未富裕到足够程度的时候,是不可行的。即使资源足够富裕,效率也不够高。总之,此思路技术实现会使得资源占用过多,可扩展性差。
IO多路复用从技术实现上又分不少种,咱们逐一来看看下述各类实现方式的优劣。
● 实现方式1:传统思路最简单的方法是循环挨个处理各个链接,每一个链接对应一个 socket,当全部 socket 都有数据的时候,这种方法是可行的。可是当应用读取某个 socket 的文件数据不 ready 的时候,整个应用会阻塞在这里等待该文件句柄,即便别的文件句柄 ready,也没法往下处理。
实现小结:直接循环处理多个链接。
问题概括:任一文件句柄的不成功会阻塞住整个应用。
● 实现方式2:select要解决上面阻塞的问题,思路很简单,若是我在读取文件句柄以前,先查下它的状态,ready 了就进行处理,不 ready 就不进行处理,这不就解决了这个问题了嘛?因而有了 select 方案。用一个 fd_set 结构体来告诉内核同时监控多个文件句柄,当其中有文件句柄的状态发生指定变化(例如某句柄由不可用变为可用)或超时,则调用返回。以后应用可使用 FD_ISSET 来逐个查看是哪一个文件句柄的状态发生了变化。这样作,小规模的链接问题不大,但当链接数不少(文件句柄个数不少)的时候,逐个检查状态就很慢了。所以,select 每每存在管理的句柄上限(FD_SETSIZE)。同时,在使用上,由于只有一个字段记录关注和发生事件,每次调用以前要从新初始化 fd_set 结构体。
1 |
|
实现小结:有链接请求抵达了再检查处理。
问题概括:句柄上限+重复初始化+逐个排查全部文件句柄状态效率不高。
● 实现方式3:poll 主要解决 select 的前两个问题:经过一个 pollfd 数组向内核传递须要关注的事件消除文件句柄上限,同时使用不一样字段分别标注关注事件和发生事件,来避免重复初始化。
实现小结:设计新的数据结构提供使用效率。
问题概括:逐个排查全部文件句柄状态效率不高。
● 实现方式4:epoll既然逐个排查全部文件句柄状态效率不高,很天然的,若是调用返回的时候只给应用提供发生了状态变化(极可能是数据 ready)的文件句柄,进行排查的效率不就高多了么。epoll 采用了这种设计,适用于大规模的应用场景。实验代表,当文件句柄数目超过 10 以后,epoll 性能将优于 select 和 poll;当文件句柄数目达到 10K 的时候,epoll 已经超过 select 和 poll 两个数量级。
实现小结:只返回状态变化的文件句柄。
问题概括:依赖特定平台(Linux)。
由于Linux是互联网企业中使用率最高的操做系统,Epoll就成为C10K killer、高并发、高性能、异步非阻塞这些技术的代名词了。FreeBSD推出了kqueue,Linux推出了epoll,Windows推出了IOCP,Solaris推出了/dev/poll。这些操做系统提供的功能就是为了解决C10K问题。epoll技术的编程模型就是异步非阻塞回调,也能够叫作Reactor,事件驱动,事件轮循(EventLoop)。Nginx,libevent,node.js这些就是Epoll时代的产物。
● 实现方式5:因为epoll, kqueue, IOCP每一个接口都有本身的特色,程序移植很是困难,因而须要对这些接口进行封装,以让它们易于使用和移植,其中libevent库就是其中之一。跨平台,封装底层平台的调用,提供统一的 API,但底层在不一样平台上自动选择合适的调用。按照libevent的官方网站,libevent库提供了如下功能:当一个文件描述符的特定事件(如可读,可写或出错)发生了,或一个定时事件发生了,libevent就会自动执行用户指定的回调函数,来处理事件。目前,libevent已支持如下接口/dev/poll, kqueue, event ports, select, poll 和 epoll。Libevent的内部事件机制彻底是基于所使用的接口的。所以libevent很是容易移植,也使它的扩展性很是容易。目前,libevent已在如下操做系统中编译经过:Linux,BSD,Mac OS X,Solaris和Windows。使用libevent库进行开发很是简单,也很容易在各类unix平台上移植。