zoukankan      html  css  js  c++  java
  • # 编写一个master worker 进程管理模型 Ps :撇开一切web server的束缚, 自己设计自己测试 #

    转自http://www.cnblogs.com/owenliang/archive/2011/12/05/2276264.html

    众所周知, 因为现在各个UNIX分支均提供了高效了"I/O复用"接口, 比如Linux下常用的epoll, 支持N万连接仍可保证高效的事件通知 . 

    也正是因为从以前select这种传统的事件驱动接口的存在, 所以依靠的事件驱动思想进行异步服务器开发的思想遍地开花 .

    Apache是靠进程/线程的数量来撑起并发接入量的, 也就是绝大多数初次接触网络编程的朋友常用的技巧, 一个Client对应一个Thread提供服务.

    但Apache随着时代的进步也在不断的改变架构, 一直没有脱离 "量变" 这个死胡同. 但是, 它先提出prefork的进程池模型, 接着master-worker+multi-threads

    应运而出, 而这个设计思想却是现在主流web服务器的基本组成部分, 不得不提得一个词 : “加锁的accept” 也是apache的独门秘籍, 这个秘籍防止了"多worker时的accept的惊群问题" .

    废话一堆, 必须回到主题, 也就是master进程如何管理worker进程的死活, 起码包括以下功能:

    1, 服务器启动之后,master平稳的启动N个worker进程,也就是worker进程的数目逐渐增加,而不是一次性全部fork完成.

    2, master进程在运行过程中, 随时监控子进程的退出状态, 也就是需要调用wait/waitpid等待子进程的退出, 包括合理的和意外的两种退出方式.

    3, master进程必须接受用户从Shell发送来的各种信号, 为了演示效果, 大概包括以下信号:

    ① SIGHUP : 重启服务器信号, 作用是平稳的杀掉原来的所有worker, 并且平稳的启动新的worker , 默认就是优雅的重启, 目的是服务器重启时对客户足够友好 , 防止颠簸严重而影响服务.

    ② SIGINT  : 优雅的关闭服务器, 作用是等待所有worker完成手头上的任务, 之后服务器退出.

    ③ SIGTERM : 暴力的关闭服务器, 作用是无论worker当前是否仍旧有任务, worker都应当立即退出.

    Ps一下 : 对于②,③两种情况,  我决定必须给它们一个期限, 因为哪一个管理员也不希望自己关闭服务器, 而服务器却迟迟不肯关闭这种怪事情发生, 所以期限一到立即发送SIGKILL给所有worker, 强行杀死进程. (SIGKILL是最凶猛的信号,无法捕获)

    下面是第一版代码, 暂时没有写"退出限期" 这个逻辑, 除此之外, 实现了"平稳的重启", "平稳的启动".

    之所以要写这个框架, 原因很简单, 因为我看大师级别的开源代码对这一部分看得比较头疼, 虽说过程都懂, 但大师写代码逻辑超前, 结果很不容易理解透彻, 所以只有按照自己的

    想法去实现, 然后体会难点与差异, 会遇到什么问题, 而且带入了自己的很多想法, 比如愚蠢的用户会连续的KILL各种信号,  比如一开始想重启SIGHUP,接着又SIGINT,SIGHUP,SIGTERM,

    乱发一通, 我的代码必须严谨的对待这些情况.

    先展示程序运行效果:

    效果图① :  手动杀死某个worker进程 , 这是为了测试worker进程意外崩溃的情况,


    part1 : master进程pid == 3000, 之后跟随10个worker进程.

    ./main &  // 我在一个终端里后台运行服务器

    //重新打开一个终端做测试(我是使用secureCRT)

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 10985 0.0 0.0 3000 700 pts/3 S 11:13 0:00 ./main
    1000 10986 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10987 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10988 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10989 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10990 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10991 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10992 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10993 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10994 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10995 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main

    part2 : 使用无敌的KILL信号杀死最后一个worker进程 ,再次观察进程列表, 发现一个崭新的worker进程11167被创建

    很奇怪的一点是11167的父进程竟然是3000而不是3004, 这就是操作系统的事情了,我们再次ps 看一下就变成3004了.


    linux-7lsl:~ # kill -SIGKILL 10995
    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 10985 0.0 0.0 3000 700 pts/3 S 11:13 0:00 ./main
    1000 10986 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10987 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10988 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10989 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10990 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10991 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10992 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10993 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10994 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 11167 0.0 0.0 3000 100 pts/3 S 11:19 0:00 ./main 

    part3 : 再做一次一样的测试 , 完全和part2测试效果一样.

    linux-7lsl:~ # kill -SIGKILL 10990

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 10985 0.0 0.0 3000 700 pts/3 S 11:13 0:00 ./main
    1000 10986 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10987 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10988 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10989 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10991 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10992 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10993 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10994 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 11167 0.0 0.0 3004 100 pts/3 S 11:19 0:00 ./main  //11167 这次变成3004了
    1000 11174 0.0 0.0 3000 100 pts/3 S 11:19 0:00 ./main  // 又是3000哦, 再ps看一次肯定也是3004了,谁知道操作系统在做什么呢.

    效果图② : 这个测试比较简单, 测试的是优雅关闭SIGINT.

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 10985 0.0 0.0 3000 700 pts/3 S 11:13 0:00 ./main
    1000 10986 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10987 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10988 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10989 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10991 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10992 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10993 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 10994 0.0 0.0 3004 100 pts/3 S 11:13 0:00 ./main
    1000 11167 0.0 0.0 3004 100 pts/3 S 11:19 0:00 ./main
    1000 11174 0.0 0.0 3004 100 pts/3 S 11:19 0:00 ./main
    linux-7lsl:~ # kill -SIGINT 10985
    linux-7lsl:~ # ps aux | grep main | grep -v grep
    linux-7lsl:~ #

    效果图③ : 同上,这次测试非优雅关闭, 必须PS一下: 我的程序里优雅和非优雅都是一样的, 而真正的服务器是如何体现它们的区别的呢?

    对于优雅关闭/重启 : worker进程关闭所有的监听套接字, 处理在线用户的剩余请求, 一直到在线用户数为0 ,则exit .

    对于非优雅关闭/重启 : worker进程立即exit , 这也就是不优雅的原因, 在线用户得到了一个掉线的不完美体验.

    和发送SIGINT是一样的结果,也就是所有进程退出了.

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    root 11498 0.0 0.0 3000 700 pts/3 S 11:36 0:00 ./main
    root 11499 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11500 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11501 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11502 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11503 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11504 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11505 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11506 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11507 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    root 11508 0.0 0.0 3004 100 pts/3 S 11:36 0:00 ./main
    linux-7lsl:~ # kill -SIGTERM 11498
    linux-7lsl:~ # ps aux | grep main | grep -v grep
    linux-7lsl:~ #


    效果图④ : 重头戏总是留到最后, 其实我认为整个master-worker进程管理的逻辑复杂点就在于重启, 代码里也有注释原因, 不过对于读者可能不便于理解 ,

    我在这里概要的描述一下重点:

    SIGHUP信号的作用是缓慢的关闭当前的worker进程, 然后缓慢的启动新的worker进程. 

    举个生动的例子: 人是细胞组成的, 人的细胞会代谢更新, 如果你的细胞先全部死光, 然后再全部重生, 人也就升天了~.~  所以我们的服务器也要避免这一个做法.

    再举个同样生动的例子: 人的细胞更新换代, 刚刚出生的细胞难道也要和那些旧死的细胞一起更新么?  当然不要, 我们吃那么多粮食可不是拿来给人体造细胞玩的.

    对应到服务器程序里,也就是上一批旧worker进程还没有全部更新完毕之前, 我们不再受理新的更新请求, 否则结果就是新的worker又被消灭, 这样颠簸的重启效果

    我们谁也不想见到.


    下面看实际效果吧:

    part1 : 仔细关注除了第一个master进程外的10个worker进程pid。

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 11939 0.0 0.0 3000 704 pts/3 S 11:55 0:00 ./main
    1000 11940 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11941 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11942 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11943 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11944 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11945 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11946 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11947 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11948 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main
    1000 11949 0.0 0.0 3004 104 pts/3 S 11:55 0:00 ./main

    part2 : 子进程全部更新换代结束, 程序的确比较快, 我们观察不到迭代过程.

    不过严谨的代码逻辑仍旧不能忽视, 因为真正的服务器可不是像我们这样的空loop,而是要处理很多I/O事件的.

    linux-7lsl:~ # kill -SIGHUP 11939 
    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 11939 0.0 0.0 3000 704 pts/3 S 11:55 0:00 ./main
    1000 11978 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11979 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11980 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11981 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11982 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11983 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11984 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11985 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11986 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main
    1000 11987 0.0 0.0 3000 104 pts/3 S 11:56 0:00 ./main

    part3 : 对比上下两段, 发现已经全部稳定了.

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 11939 0.0 0.0 3000 704 pts/3 S 11:55 0:00 ./main
    1000 11978 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11979 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11980 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11981 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11982 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11983 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11984 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11985 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11986 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11987 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main


    part4 : 顺便在这里做一个综合测试吧, 我们杀掉一个新的worker进程,看看master是否能够正确的重启拉起.

    对比上下两段数据的加粗部分, 结果显而易见.

    linux-7lsl:~ # kill -SIGKILL 11986
    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 11939 0.0 0.0 3000 704 pts/3 S 11:55 0:00 ./main
    1000 11978 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11979 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11980 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11981 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11982 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11983 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11984 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11985 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11987 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 12049 0.0 0.0 3000 104 pts/3 S 11:59 0:00 ./main


    part5 : 我们最后终结服务器, 完成这个程序的测试.

    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 11745 0.1 0.4 7764 4180 pts/3 T 11:45 0:01 vim main.cpp
    1000 11939 0.0 0.0 3000 704 pts/3 S 11:55 0:00 ./main
    1000 11978 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11979 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11980 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11981 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11982 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11983 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11984 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11985 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 11987 0.0 0.0 3004 104 pts/3 S 11:56 0:00 ./main
    1000 12049 0.0 0.0 3000 104 pts/3 S 11:59 0:00 ./main
    linux-7lsl:~ # kill -SIGINT 11939
    linux-7lsl:~ # ps aux | grep main | grep -v grep
    1000 11745 0.1 0.4 7764 4180 pts/3 T 11:45 0:01 vim main.cpp
    linux-7lsl:~ #


    好了, 下面是代码, 不做讲解了, 大家根据注释尽量的阅读吧, 必须PS一下: 这完全是我个人设计的,基本没有模仿现有的web服务器, 很多逻辑都是自己思考的, 严谨的对待了用户随意KILL的情况, 如果你能读懂细节的话, 或者自己也去尝试编写, 应该会体会到我的用意.

    Fixup:

      ① 提交之前vim没有:w ,提交了一个BUG版 ,  现已修正 , 问题出现在srv_restart的判定语句逻辑不正确,如果不是高频率KILL sighup将无法重现该bug.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/wait.h>
    #include <string.h>

    volatile sig_atomic_t srv_restart = 0;
    volatile sig_atomic_t srv_graceful_end = 0;
    volatile sig_atomic_t srv_ungraceful_end = 0;
    volatile sig_atomic_t sigalrm_recved = 0;

    void signal_handler(int signo)
    {
    switch (signo)
    {
    case SIGTERM:
    srv_ungraceful_end = 1;
    break;
    case SIGINT:
    srv_graceful_end = 1;
    break;
    case SIGHUP:
    srv_restart = 1;
    break;
    case SIGALRM:
    sigalrm_recved = 1;
    break;
    }
    }

    void child_loop()
    {
    sigalrm_recved = 0;

    while (true)
    {
    sleep(5);
    printf("worker pid : %d\n\n", getpid());

    // 这里没有上下文,就不演示grace/ungrace的区别了
    if (srv_ungraceful_end || srv_graceful_end || srv_restart)
    {
    exit(0);
    }

    // worker能否继承定时器呢,我自己做个测试,因为懒得翻APUE.
    if (sigalrm_recved)
    {
    printf("worker pid : %d can recv alarm\n", getpid());
    }
    }
    }

    typedef struct
    {
    pid_t pid; //default 0
    int state; //default 0 , 表示空闲
    }proc_info;

    #define CHILD_NUM 10
    proc_info child_info[CHILD_NUM];


    int main()
    {
    int num_children = 0;
    int restart_finished = 1;
    int is_child = 0;

    // register signal handler
    struct sigaction act;
    bzero(&act, sizeof(act));
    act.sa_handler = signal_handler;

    sigaction(SIGALRM, &act, NULL);
    sigaction(SIGTERM, &act, NULL);
    sigaction(SIGINT , &act, NULL);
    sigaction(SIGHUP , &act, NULL);

    // 设置信号屏蔽字,阻塞除此4信号外所有信号
    sigset_t set;
    sigfillset(&set);
    sigdelset(&set, SIGALRM);
    sigdelset(&set, SIGTERM);
    sigdelset(&set, SIGINT);
    sigdelset(&set, SIGHUP);

    sigprocmask(SIG_SETMASK, &set, NULL);

    // 设置定时器, 用于控制master进程的loop频率
    // 定时不同,意味着master进程监管worker进程的实时性
    // 这里设置为1秒

    struct itimerval timer;
    timer.it_value.tv_sec = 1;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 1;
    timer.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &timer, NULL);

    while (true)
    {
    pid_t pid;

    if (num_children != 0)
    {
    pid = wait(NULL);

    if (pid != -1)
    {
    num_children--;

    for (int i = 0; i < CHILD_NUM; ++ i)
    {
    if (child_info[i].pid == pid)
    {
    child_info[i].pid = 0;
    child_info[i].state = 0;
    break;
    }
    }
    }
    }

    if (srv_graceful_end || srv_ungraceful_end)
    {
    if (num_children == 0)
    {
    break; //get out of while(true)
    }

    for (int i = 0; i < CHILD_NUM; ++ i)
    {
    if (child_info[i].pid != 0)
    {
    // 假设用户递交了SIGINT和SIGTERM这种变态情况
    // 优先选择优雅退出SIGINT
    kill(child_info[i].pid, srv_graceful_end ? SIGINT : SIGTERM);
    }
    }

    // 假设用户递交了SIGINT/SIGTERM,并且也提交了SIGHUP这种变态情况
    // 我决定忽略SIGHUP, 也就是在重启和关闭之间选择了关闭
    continue; //this is necessary.
    }

    // 服务器的重启是优雅并且平稳的
    // 每一轮循环, 只对其中一个旧worker发送SIGHUP
    // 大概的意思 : 关闭一个旧的才会重启一个新的
    // restart_finished很重要,目的是防止用户
    // 连续递送多个SIGHUP信号,那将不断的导致
    // 挂断我们新启动的worker,而这是毫无意义的
    // 所以,在上一次重启没结束前,不接纳新的SIGHUP

    if (srv_restart)
    {
    srv_restart = 0;

    if (restart_finished)
    {
    restart_finished = 0;

    for (int i = 0; i < CHILD_NUM; ++ i)
    {
    if (child_info[i].pid == 2) //每个正在工作的worker
    {
    child_info[i].state = 1; //1表示待重启
    }
    }
    }
    }

    if (!restart_finished)
    {
    int ndx;

    for (ndx = 0; ndx < CHILD_NUM; ++ ndx)
    {
    if (child_info[ndx].state == 1)
    {
    break;
    }
    }

    // 上一次发送SIGHUP时正处于工作的worker已经全部
    // 重启完毕.
    if (ndx == CHILD_NUM)
    {
    restart_finished = 1;
    }
    else
    {
    kill(child_info[ndx].pid, SIGHUP);
    //child_info[ndx].state = 3; 重启中

    //还是为了尽量避免连续的SIGHUP的不良操作带来的颠簸
    //,所以决定取消(3重启中)这个状态
    //并不是说连续SIGHUP会让程序出错,只是不断的挂掉新进程很愚蠢
    }
    }

    // 信号处理结束,尽可能多得启动worker进程
    // 这种想法是合理的,重启时的关闭应该平稳缓慢
    // 但worker进程应该尽快拉起以便恢复服务

    for (int i = 0; i < CHILD_NUM; ++ i)
    {
    // 启动所有空闲的worker
    if (child_info[i].state == 0)
    {
    pid = fork();

    switch (pid)
    {
    case -1:
    break;
    case 0:
    is_child = 1;
    break;
    default:
    ++ num_children;
    child_info[i].state = 2;
    child_info[i].pid = pid;
    break;
    }

    if (!pid) break; //子进程
    }
    }

    if (!pid) break; //子进程跳出while
    }

    if (!is_child) //父进程从while跳出,一定是srv_graceful/ungraceful_end
    {
    exit(0); //父进程退出
    }

    //子进程执行自己的child_loop;

    child_loop();

    return 0; //both master and worker never reach here
    }


  • 相关阅读:
    每日英语:Universities in Singapore strengthen cooperation with China
    每日英语:The Exercise Equivalent of a Cheeseburger?
    每日英语:Eating Safely in China, on a Budget
    每日英语:What Makes A RiskTaker
    OAuth2 vs JWT,到底怎么选?
    Docker 为什么输给了Kubernetes?Docker 员工自述!
    90 岁程序员,他的压缩算法改变了世界!
    数据库设计的 10 个最佳实践!
    代码写的垃圾被嫌弃?这 3 个插件你值得拥有!
    ShardingJdbc 实现读写分离 + 分库分表,写得太好了!
  • 原文地址:https://www.cnblogs.com/balaamwe/p/2300665.html
Copyright © 2011-2022 走看看