服务器程序设计范式(UNP CHP3)

  1. 个请求1个进程.(one process per client)

  2. 增强型预先派生 N 个子进程(pre-fork)

减少进程创建开销. 2017-11-24-14-27-24

注意惊群现象: 多个进程同时 accept 同一个 listen fd, 因为 fork 进程时,如果没有 close_on_exec, 子进程增加父进程 fd 的引用。 当有连接到来时,进程同时唤醒,(但并不是 accept 返回)。 但只有最先运行的子进程获得 client 连接,其他继续睡眠,每次都这样也是有开销的。这就是“惊群”现象

  1. prefork accept 上锁

并不是所有内核的 accept 都能如此使用,安全的做法是给 accept 上锁。只有1个进程阻塞在 accept 中,其他阻塞在锁上。

上进程锁

能用文件锁,另外的技巧是创建文件锁的文件后,立即 unlink 掉,这样程序崩溃也不会有临时文件,但是进程还在,文件锁的功能可用

上进程共享的互斥锁

首先锁必须设置 PTHREAD_PROCESS_SHARED 属性。 然后锁必须在共享内存上,如用 mmap 创建一段共享内存,然后把锁创建到上面

  1. prefork 传递 fd

不同于在子进程中 accept,而是父进程 accept 后,把 fd 传递给“空闲”的子进程 具体是在父进程创建 socketpair 留一个管道,遍打通了父子的通道. 2017-11-24-16-31-29

2017-11-24-17-02-35

主进程用 select 做事件驱动,一旦有事件,查询到空闲子进程,便把数据转发到该进程处理。 这种方式比前面的 accept 多了很多 pipe 传递数据,效率比较低。

  1. 每个客户请求1个线程 (one thread per client)

最直白,用的最多,显然比 prefork 要快。 在客户后,现场生成子线程。 accept 在 main 中,也就是无连接时,主程序大部分时间都阻塞在 accept 里。

  1. pre-thread

先创建线程池,是主线程 accept,把新的请求 fd 提交到线程池中的某个可用线程,又快一点 因此2个动作少不了:

主线程的put提交new fd的动作

线程池里 get 获得活动的fd的动作

  1. lock+accept

先创建线程池,让每个线程自身 accept,用锁保护 accept,保证每个时刻只有一个线程在 accept 比方式 5 又要快一点

值得学习的库

libevent

acl

muduo

libco 微信用的 c++协程库