Redis Source Code Read Log( 4. fork issuse)

Part 1: 标准I/O的缓冲作用

标准I/O带缓冲,那么作用是什么呢?简单而言,就是为了提高效率,防止反复系统调用,导致的性能问题。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    printf("CLOCKS_PER_SEC=%ld\n", CLOCKS_PER_SEC);
    int loopcnt = 100 * 100 * 100;
    clock_t b = 0;
    clock_t e = 0;

    char bf[1024 * 8];
    bf[0] = 0;

    char data[] = "Hello, World !\n";
    int len = sizeof(data) - 1;

    FILE* fp = fopen("isoc_write.txt", "wb");

    int fd = open("linux_write.txt", O_CREAT|O_TRUNC|O_WRONLY, 0664);
    if (fd < 0)
    {
        perror("open");
        return 0;
    }

    int fd2 = open("linux_write_with_buf.txt", O_CREAT|O_TRUNC|O_WRONLY, 0664);
    if (fd2 < 0)
    {
        perror("open");
        return 0;
    }

    int i;
    b = clock();
    for (i = 0; i < loopcnt; i++)
    {
        fwrite(data, len, 1, fp);
    }
    e = clock();
    printf("ISOC_fwrite: %ld\n", e - b);
    fclose(fp);

    b = clock();
    for (i = 0; i < loopcnt; i++)
    {
        if (write(fd, data, len) < -1)
        {
            perror("write"), exit(0);
        }
    }
    e = clock();
    printf("Linux_write: %ld\n", e - b);
    close(fd);

    b = clock();
    int cnt = 0;
    for (i = 0; i < loopcnt; i++)
    {
        if (cnt + len >= 1024 * 8)
        {
            if (write(fd2, bf, cnt) < 0)
            {
                perror("write"), exit(0);
            }
            cnt = 0;
        }
        memcpy(bf+cnt, data, len);
        cnt += len;
    }
    if (cnt > 0)
    {
        if (write(fd2, bf, cnt) < 0)
        {
            perror("write"), exit(0);
        }
    }
    e = clock();
    printf("Linux_write_with_buf: %ld\n", e - b);
    close(fd2);

    return 0;
}

open 的 mode用 0664,这样出来的文件权限与ISO C 的文件权限是一致的。

测试结果来看,直接write效率极低。自缓存8K 的效率貌似还略高于ISO C。

此处Mark提醒。了解即可。

Part 2: 僵尸进程

当子进程退出,父进程扔持续运行,但是没有处理子进程的退出状态。那么子进程虽然退出,但是依旧在系统中,以一个僵尸进程的状态存在。这并不符合预期,应尽量避免。僵尸进程实际已经退出,那么他不在响应任何信号。那么即便 kill -9 也是无用的,这把骨灰,总是没法儿处理。

父进程没有处理是什么意思呢?

1. 没有调用wait系列的函数,针对子进程进行对应的处理。

2. 子进程退出会发送 SIGCHLD 信号,而系统默认情况是,父进程不捕捉该信号。父进程采用了这种默认方式。

wait系列函数,提供了阻塞与非阻塞两种方式,让父进程对子进程退出状态进行处理。尤其是waitpid函数,提供了多种功能。可以针对各种子进程进行处理。

SIGCHLD信号,父进程如果捕捉,即便采用signal(SIGCHLD,SIG_IGN)的方式,亦可以。但是 SIGCHLD为不可靠信号。多个子进程的情况下,SIGCHLD信号,极有可能丢失。所以这种信号方式,并不十分靠谱。

wait系列函数,阻塞的方式,可以保证所以子进程能够被顺利回收,但是,父进程阻塞... 当采用非阻塞方式,那么父进程就需要不断轮询,查看子进程是否退出(redis采用的就是这种方式,在serverCron轮询任务中进行wait3的非阻塞子进程退出处理的操作)。非阻塞轮询方式,在影响父进程运行较小的情况下,极大限度的缩短了子进程的僵尸进程状态的持续时间,但是僵尸进程还是不可避免的。

Part 3: 孤儿进程

父进程退出,子进程仍在运行,那么,子进程就成为了孤儿进程。这种也是不允许的。Linux上,每个进程必须有且仅有一个父进程(0号进程,内核进程;1号进程,init进程;有些平台有2号进程,内存页守护进程不在此列)。不过,应用层编程人员而言,这一点比较轻松,父进程退出,子进程不需要捕捉这种状态,系统会将孤儿进程,塞进孤儿院,即重新指定其父进程为init进程。

Part 4: 僵尸进程的避免,孤儿进程的应用

一般的,如何避免子进程成为僵尸进程,而父进程需要或者长时间阻塞wait或者不断轮训非阻塞wait。但是如果子进程过多,又需要长时间的运行,这种方式对父进程而言,代价比较大。

上图就是一种常用的方式,父进程 启动子进程,wait 等待子进程退出,子进程启动真正的子进程也就是孙子进程,启动之后,子进程退出。孙子进程便成为孤儿,托管进孤儿院。原先的父进程,处理了中间过渡子进程的退出,后继续运行。

Part 4: 守护进程

守护进程,采用了类似上面的方式,但是,细节场景更为复杂,比如进程组,会话,控制终端等等。后续详细讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值