进程池和线程池

池的概念

由于server的硬件资源“充裕”。那么提高server性能的一个非常直接的方法就是以空间换时间。即“浪费”server的硬件资源。以换取其执行效率。这就是池的概念。

池是一组资源的集合,这组资源在server启动之初就全然被创建并初始化,这称为静态资源分配。

当server进入正是执行阶段。即開始处理客户请求的时候。假设它须要相关的资源,就能够直接从池中获取,无需动态分配。非常显然,直接从池中取得所需资源比动态分配资源的速度要快得多。由于分配系统资源的系统调用都是非常耗时的。当server处理完一个客户连接后,能够把相关的资源放回池中,无需执行系统调用来释放资源。

从终于效果来看。池相当于server管理系统资源的应用设施,它避免了server对内核的频繁訪问。

池能够分为多种,常见的有内存池、进程池、线程池和连接池。

进程池和线程池概述

进程池和线程池相似,所以这里我们以进程池为例进行介绍。如没有特殊声明,以下对进程池的讨论全然是用于线程池。

进程池是由server预先创建的一组子进程。这些子进程的数目在3~10个之间(当然这仅仅是典型情况)。线程池中的线程数量应该和CPU数量差点儿相同。

进程池中的全部子进程都执行着同样的代码,并具有同样的属性,比方优先级、PGID等。

当有新的任务来到时。主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程。选择一个已经存在的子进程的代价显得小得多。

至于主进程选择哪个子进程来为新任务服务,则有两种方法:

  1. 主进程使用某种算法来主动选择子进程。最简单、最经常使用的算法是随机算法和Round Robin(轮流算法)。

  2. 主进程和全部子进程通过一个共享的工作队列来同步。子进程都睡眠在该工作队列上。当有新的任务到来时。主进程将任务加入到工作队列中。

    这将唤醒正在等待任务的子进程,只是仅仅有一个子进程将获得新任务的“接管权”,它能够从工作队列中取出任务并运行之,而其它子进程将继续睡眠在工作队列上。

当选择好子进程后,主进程还须要使用某种通知机制来告诉目标子进程有新任务须要处理,并传递必要的数据。最简单的方式是。在父进程和子进程之间预先建立好一条管道,然后通过管道来实现全部的进程间通信。在父线程和子线程之间传递数据就要简单得多,由于我们能够把这些数据定义为全局。那么它们本身就是被全部线程共享的。

综上所述,进程池的一般模型例如以下所看到的:

处理多客户

在使用进程池处理多客户任务时,首先要考虑的一个问题是:监听socket和连接socket是否都由进程统一管理这两种socket。这能够一下介绍的并发模式解决。

server主要有两种并发编程模式:半同步/半异步模式和领导者/追随者模式。

其次,在设计进程池时还须要考虑:一个客户连接上的全部任务是否始终由一个子进程来处理。

假设说客户任务是无状态的,那么我们可以考虑使用不同的子进程来为该客户的不同请求服务。但假设客户是存在上下文关系的。则最好一直用同一个子进程来为之服务,否则实现起来比較麻烦。由于我们不得不在各子进程之间传递上下文数据。epoll的EPOLLONESHOT事件可以确保一个客户连接在整个生命周期中仅被一个线程处理。

半同步/半异步模式

在并发模式中,同步指的是程序全然依照代码序列的顺序运行;异步指的是程序的运行须要由系统事件来驱动。

常见的系统事件包含中断、信号等。例如以下描写叙述了同步的读操作和异步的读操作。

依照同步方式执行的线程称为同步线程,依照异步方式执行的线程称为异步线程。

显然,异步线程的执行效率高,实时性强,但编程相对复杂,难于调试和扩展,不适合大量的并发。二同步线程则相反,尽管效率较低。实时性较差。但逻辑简单。因此。对于像server这样的既要求较好的实时性,又要求能处理多个客户请求的应用程序。我们就应该同一时候使用同步线程和异步线程来实现,即採用半同步/半异步模式实现。

半同步/半异步模式中,同步线程用于处理客户逻辑,异步线程用于处理I/O时间。

异步线程监听到客户请求后。就将其封装成请求对象并插入请求队列中。请求队列将通知某个工作在同步模式的工作线程来读取并处理请求对象。

详细选择哪个工作线程来为新的客户请求服务,则取决于请求队列的设计。

比方简单的轮流选取工作线程的Round
Robin算法。也能够通过条件变量或信号量来随机地选择一个工作线程。

半同步/半反应堆(half-sync/half-reactive)模式

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2Fsa2Vya2Fscg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

上图中异步线程仅仅有一个,由主线程来充当。

它负责监听全部socket上的事件。假设监听socket上有可读事件发生。即有新的连接请求到来。主线程就接受之以得到新的连接socket,然后往epoll内核事件表中注冊该socket上的读写事件。假设连接socket上有读写事件发生。即由新的客户请求到来或者有数据发送至client。主线程就将该连接socket插入请求队列中。全部工作线程都睡眠在请求队列上。当有任务到来时。它们通过竞争获得任务的接管权。

这样的竞争机制使得空暇的工作线程才有机会来处理新任务。这是非常合理的。

主线程插入请求队列中的任务是就绪的连接socket。

这说明该图所看到的的半同步/半反应堆模式採用的时间处理模式是Reactor模式。它要求工作线程自己从socket上读取客户请求和往socket写入server应答。这就是该模式的名称中half-reactive的含义。实际上。也能够使用Proactor时间处理模式,即由主线程来完毕数据的读写。

在这样的请求下。主线程通常会将应用程序数据、任务类型等信息封装为一个任务对象。然后将其插入请求队列。工作线程从请求队列中取得任务对象中之后,就可以直接处理之,而无须运行读写操作了。

半同步/半反应堆存在例如以下缺点:

  1. 主线程和工作线程共享请求队列。

    主线程往请求队列中加入任务,或者工作线程从请求队列中取出任务。都须要对请求队列加锁保护,从而浪费CPU时间。

  2. 每一个工作线程在同一时间仅仅能处理一个客户请求。假设客户数量较多。而工作线程较少。则请求队列中将堆积非常多任务对象。client的响应速度将越来越慢。

    假设通过添加工作线程来解决这一问题,则工作线程的切换也将耗费大量CPU时间。

高效的半同步/半异步模式

上图中。。主线程仅仅管理监听socket,连接socket由工作线程来管理。当有新的连接到来时,主线程就接受之并将新返回的连接socket派发给某个工作线程,此后该新socket上的不论什么I/O操作都被选中的工作线程处理。知道客户关闭连接。

主线程向工作线程派发socket的最简单的方式,是往它和工作线程之间的管道里写数据。工作线程检測到管道上有数据可读时,就分析是否是一个新的客户连接请求到来。假设是,则把该新的socket上的读写事件注冊到自己的epoll内核事件表中。

每一个线程都维持自己的时间循环。他们各自独立地监听不同的时间。因此。在这样的高效的半同步/半异步模式中,每一个线程都工作在异步模式,全部它并不是严格意义上的半同步/半异步模式。

领导者/追随者模式

领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件的一种模式。在随意时间点,程序都仅有一个领导者线程,它负责监听I/O时间。而其它线程则都是追随者,他们休眠在线程池中等待成为新的领导者。当前的领导者假设检測到I/O事件,首先要从线程池中推选出新的领导者线程。然后处理I/O事件。

此时。新的领导者等待新的I/O事件,二者实现了并发。

多个子进程。

半同步/半异步进程池实现

这里我们实现一个基于高效的半同步/半异步并发模式的进程池。为了避免父、子进程之间传递文件描写叙述符。我们将接受新连接的操作放到子进程中。非常显然,对于这样的模式而言,一个客户连接上的全部任务始终由一个子进程来处理。

代码清单见https://github.com/walkerczb/processpool下的processpool.h

用进程池实现的CGIserver

然后利用建立的进程池。实现了一个CGIserver。

代码清单见https://github.com/walkerczb/processpool下的processpool.c

运行server程序例如以下:

chen123@ubuntu:~/LinuxServer Programming$
./processpool192.168.73.129 54321

client运行telnet 192.168.73.129123123后显示结果例如以下:

li123@ubuntu:~$ telnet 192.168.73.129 54321

Trying192.168.73.129...

Connected to192.168.73.129.

Escape characteris '^]'.

printHelloworld (server有printHelloworld程序运行后显示HelloWorld)

Hello World!

Connectionclosed by foreign host.

server上显示:

send request tochild 0

user contentis:printHelloworld

上面显示中粗体为敲入命令。其余为运行结果。

半同步/半反应堆线程池的实现

这里我们实现了一个半同步/半反应堆并发模式的线程池,代码清单见https://github.com/walkerczb/threadpool中的threadpool.h

该线程池使用一个工作队列全然解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务。工作线程通过竞争来取得任务并运行它。

只是,假设要将该线程池应用到实际server程序中,那么我们必须保证全部客户请求都是无状态的,由于同一个连接上的不同请求可能会由不同的线程处理。

这里值得一提的是。在C++程序中使用pthread_create函数时。该函数的第3个參数必须指向一个静态函数。而要在一个静态函数中使用类的动态成员(包含成员函数和成员变量)。则仅仅能通过例如以下两种方式实现:

  1. 通过类的静态对象来调用。

  2. 将类的对象作为參数传递给该静态函数。然后在静态函数中引入这个对象,并调用其动态方法。

代码清单threadpool.h使用的另外一种方式:将线程參数设置为this指针,然后在worker函数中获取该指针并调用其动态方法run。

用线程实现的简单Webserver

这里我们用前面的线程池来实现一个并发的Webserver

http_conn类

首先,我们须要准备线程池的模板參数类,用以封装对逻辑任务的处理。这个类是http_conn。代码清单见头文件https://github.com/walkerczb/threadpool 中的http_conn.h,
http_conn.cpp和locker.h。

main函数

定义好任务之后,main函数就变得非常easy了,它仅仅须要负责I/O读写。如代码清单https://github.com/walkerczb/threadpool 中的main.cpp。

版权声明:本文博主原创文章,博客,未经同意不得转载。

Linux高性能server规划——处理池和线程池的更多相关文章

  1. Linux高性能server规划——多进程编程

    多进程编程 多进程编程包含例如以下内容: 复制进程影映像的fork系统调用和替换进程映像的exec系列系统调用. 僵尸进程以及怎样避免僵尸进程 进程间通信(Inter-Process Communic ...

  2. Linux高性能server规划——多线程编程(在)

    多线程编程 Linux主题概述 线程模型 线程是程序中完毕一个独立任务的完整执行序列.即一个可调度的实体. 依据执行环境和调度者的身份.线程可分为内核线程和用户线程.内核线程,在有的系统上也称为LWP ...

  3. Linux + C + Epoll实现高并发服务器(线程池 + 数据库连接池)(转)

    转自:http://blog.csdn.net/wuyuxing24/article/details/48758927 一, 背景 先说下我要实现的功能,server端一直在linux平台下面跑,当客 ...

  4. Linux网络通信(线程池和线程池版本的服务器代码)

    线程池 介绍 线程池: 一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的 ...

  5. paip.提升性能----数据库连接池以及线程池以及对象池

    paip.提升性能----数据库连接池以及线程池以及对象池 目录:数据库连接池c3po,线程池ExecutorService:Jakartacommons-pool对象池 作者Attilax  艾龙, ...

  6. 进程池与线程池基本使用、协程理论与实操、IO模型、前端、BS架构、HTTP协议与HTML前戏

    昨日内容回顾 GIL全局解释器锁 1.在python解释器中 才有GIL的存在(只与解释器有关) 2.GIL本质上其实也是一把互斥锁(并发变串行 牺牲效率保证安全) 3.GIL的存在 是由于Cpyth ...

  7. 05网络并发 ( GIL+进程池与线程池+协程+IO模型 )

    目录 05 网络并发 05 网络并发

  8. python系列之 - 并发编程(进程池,线程池,协程)

    需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...

  9. 第三十八天 GIL 进程池与线程池

    今日内容: 1.GIL 全局解释器锁 2.Cpython解释器并发效率验证 3.线程互斥锁和GIL对比 4.进程池与线程池 一.全局解释器锁 1.GIL:全局解释器锁 GIL本质就是一把互斥锁,是夹在 ...

随机推荐

  1. js:进一步关闭(范围:下一个)

    function fn1(){   //创建一个数组   var fns = new Array();   //i这个变量是保存在fn1这个作用域中   for(var i=0;i<10;i++ ...

  2. ubuntu 12.10 软件更新源列表

    ubuntu 12.10正式版已经发布了,国内各大开源软件源也陆续更新了资源.今天分享一下ubuntu 12.10 软件更新源列表. 首先,习惯性的备份一下ubuntu 12.04 原来的源地址列表文 ...

  3. devstack安装使用openstack常见问题与解决的方法

    声明: 本博客欢迎转发,但请保留原作者信息! 博客地址:http://blog.csdn.net/halcyonbaby 内容系本人学习.研究和总结.如有雷同,实属荣幸! 安装执行create-sta ...

  4. UVALive 3890 Most Distant Point from the Sea(凸包最大内接园)

    一个n个点的凸多边形,求多边形中离多边形边界最远的距离.实际上就是求凸包最大内接圆的半径. 利用半平面交求解,每次二分枚举半径d,然后将凸包每条边所代表的半平面沿其垂直单位法向量平移d,看所有平移后的 ...

  5. hibernate 大对象类型hibernate制图

    基础知识: 在 Java 在, java.lang.String 它可以用来表示长串(超过长度 255), 字节数组 byte[] 可用于存放图片或文件的二进制数据. 此外, 在 JDBC API 中 ...

  6. 和学生探讨吉林大学python问题

    学生们真的很强大,我知道玩微信,nodejs.... 我们去学校了解.当时互联网开始64K....

  7. WCF异常传播

    传送至客户端的异常肯定是CommunitionException类型,包括一般的通信过程中出错而引发的CommunicationException类型,System.IdentityModel.Sel ...

  8. UVA 10831 - Gerg&#39;s Cake(数论)

    UVA 10831 - Gerg's Cake 题目链接 题意:说白了就是给定a, p.问有没有存在x^2 % p = a的解 思路:求出勒让德标记.推断假设大于等于0,就是有解,小于0无解 代码: ...

  9. 局域网连接SQL Server数据库配置

    首先要保证两台机器位于同一局域网内,然后打开配置工具→SQL Server配置管理器进行配置.将MSSQLSERVER的协议的TCP/IP的(IP1.IP2)TCPport改为1433,已启用改为是. ...

  10. java基础---->Zip压缩的使用(转)

    java中提供了对压缩格式的数据流的读写.它们封装到现成的IO 类中,以提供压缩功能.下面我们开始java中压缩文件的使用. 目录导航: 关于压缩的简要说明 GZIP压缩文件的使用 ZIP压缩文件的使 ...