• 关于文件缓冲的问题


    有三种类型的缓冲策略:

    无缓冲,块缓冲和行缓冲。

    当输出流无缓冲时,信息在写的同时出现于目标文件或终端上;

    当是块缓冲时,字符被暂存,然后一起写入;

    当是行缓冲时,字符被暂存,直到要输出一个新行符,或者从任何与终端设备连接的流中 (典型的是 stdin) 读取输入时才输出。

    函数 fflush(3) 可以用来强制提前输出。(参见 fclose(3)) 通常所有文件都是块缓冲的。

    当文件 I/O 操作在文件上发生时,将调用 malloc(3) ,获得一个缓冲。

    如果流指向一个终端 (通常 stdout 都是这样),那么它是行缓冲的。

    标准错误流 stderr 默认总是无缓冲的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    setvbuf 可以用在任何打开的流上,改变它的缓冲。
    函数声明:int setvbuf(FILE *stream, char *buf, int mode , size_t size);
     
    参数说明:
    stream - 流指针
     
    buf - 缓冲区
     
    mode - 必须是下列三个宏之一:
    _IONBF 无缓冲
    _IOLBF 行缓冲
    _IOFBF 完全缓冲
     
    size - 缓冲区大小

    除非是无缓冲的文件,否则参数 buf 应当指向一个长度至少为 size 字节的缓冲;这个缓冲将取代当前的缓冲。如果参数 buf 是 NULL ,只有这个模式会受到影响;下次 read 或 write 操作还将分配一个新的缓冲。

    函数 setvbuf 只能在打开一个流,还未对它进行任何其他操作之前使用。

    我们出现了一个问题:程序的日志输出到了终端上,但是没有输出到日志中。

    问题分析:

    1.输出到了终端上,因为指向终端的流是行缓冲的。

    2.写入文件的日志由于是块缓冲,但是该程序的日志比较少,没有写满缓冲块的时候则不会写入文件。

    问题解决:

    1.通fflush可以把缓冲区内容刷到文件中

    2.通过setvbuf接管缓冲区,自己控制缓冲区大小,以及缓冲模式

    3.另外当程序由于终止时(收到结束信号等),也不会把缓冲内容刷到缓冲区中。

    测试程序以及验证方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    test.c:
     
    #include "stdio.h"
    #include "stdlib.h"
    #include "stdarg.h"
    #include "time.h"
     
    static FILE *log_file = NULL;
     
    int dbgprintf(const char *format, ...)
    {
        va_list args;
        va_start(args, format);
        if (log_file)
            vfprintf(log_file, format, args);
        else
            vfprintf(stdout, format, args);
        va_end(args);
    }
     
    int main(void)
    {
        time_t  curtime;
        struct tm *now;
     
        log_file = fopen("/tmp/logtest.txt", "a+");
     
    //设置自己的缓冲区
        char buf[1000];
        setvbuf(log_file, buf,_IOFBF,1000); //注意句柄和mode
         
    //循环执行,每次输出当前时间以及循环次数
        int nLoopTime = 0;
        while(1)
        {
            nLoopTime++;
     
            time(&curtime);
            now = localtime(&curtime);
     
    //dbgprintf是输出到文件中
            dbgprintf( " %d-%d-%d %d:%d:%d------------------- ",
                            now->tm_year+1900,now->tm_mon+1,now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec );
             
            dbgprintf("hihi,%d. ", nLoopTime);
            dbgprintf("hihi,%s. ", "ohyeah1");
            dbgprintf("hihi,%s. ", "ohyeah2");
            dbgprintf("hihi,%s. ", "ohyeah3");
            dbgprintf("hihi,%s. ", "ohyeah4");
             
    //printf是stdout,输出到终端
            printf("loop:nLoopTime=%d. ", nLoopTime);
            sleep(1);
        }
     
        return 1;
    }

    从以上程序可以看到,当前的缓冲模式为_IOFBF,也就是块缓冲,当缓冲写满1000时,才会刷到文件中。

    程序编译:

    1
    gcc -o ack test.c

    执行程序,可以看到printf在终端上的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@localhost logtest]# ./ack
    loop:nLoopTime=1.
    loop:nLoopTime=2.
    loop:nLoopTime=3.
    loop:nLoopTime=4.
    loop:nLoopTime=5.
    loop:nLoopTime=6.
    loop:nLoopTime=7.
    loop:nLoopTime=8.
    loop:nLoopTime=9.

    查看日志文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [root@localhost tmp]# tail -f logtest.txt
    2013-11-14 10:18:30-------------------
    hihi,ohyeah1.
    hihi,ohyeah2.
    hihi,ohyeah3.
    hihi,ohyeah4.
     
    2013-11-14 10:18:31-------------------
    hihi,ohyeah1.
    hihi,ohyeah2.
    hihi,ohyeah3
    2013-11-14 10:28:19-------------------
    hihi,1.
    hihi,ohyeah1.
    hihi,ohyeah2.
    hihi,ohyeah3.
    hihi,ohyeah4.
     
    2013-11-14 10:28:20-------------------
    hihi,2.
    hihi,ohyeah1.
    hihi,ohyeah2.
    hihi,ohyeah3.
    hihi,ohyeah4.
     
    2013-11-14 10:28:21-------------------

    仔细观察可以发现,printf和日志是不同步的,日志中的信息是一块一块刷出来的。

    换个模式试试,把setvbuf的mode改为_IOLBF或者_IONBF,则会发现日志立刻就写进去了。

    当然,也可以试试用fflush刷进去,效果是一样的。

    比较简单,就不演示了。

    下面转了一些概念过来:

    对于写操作通常我们会遇到两个两个缓冲 (buffer):

    一个是内核缓冲。 当我们调用write写文件时,write返回之后其实内容并没有立刻写到硬盘上,而是写到了内核的缓存中。什么时候写到磁盘?内核有一套刷缓存的机制。这样做有很明显的好处,比如我们调用1次write写1kb和调用1k次write每次写1b的数据,所花的时间是差不多的。后者所花的用户态/内核态切换时间多些,但是写磁盘的次数却是一样的。这样就大大提高了效率。

    另外一个是glibc维护的用户态缓冲。 这个缓冲又是用来干什么的呢?内核和硬盘是两个相对独立的系统,内核缓冲在这两个之间避免了很多不必要的同步。那么同样,内核和用户程序也是两个相对独立的系统,每次系统调用也是要花代价的。所以上面1次write写1kb和调用1k次write每次写1b的数据的例子,前后两种方法还是有差距的,差距就在于后者需要做1k此用户态和内核态的切换。所以,glibc在用户态上又做了一个缓冲。当我们调用glibc提供的printf输出的时候,并没有直接映射到一次write系统调用,而是存在了glibc管理的缓冲中,当条件满足时(下面会说上面时候满足)再调用一次write,把用户态的缓冲写到内核态去。所以,调用1此printf到文件1kb字符和1k此print每次1个字符,所花的时间就真差不多了。

    块缓冲:

    第一次执行 I/O 操作时,ANSI 标准的文件管理函数通过调用

    malloc 函数获得需使用的缓冲区。默认大小为 8192。

    行缓冲:

    在这种情况下,当在输入和输出中遇到换行符时,标准 I/O 库执行 I/O

    系统调用操作。当流涉及一个终端时(例如标准输入和标准输出),使用行缓冲区。因为标准I/O 库收集的每行的缓冲区长度是固定的,只要填满了缓冲区,即使还没有遇到换行符,也将执行 I/O 系统调用操作。默认行缓冲区大小为 128 字节。

    无缓冲:

    标准 I/O 库不对字符进行缓存。如果用标准 I/O 函数写若干字符到不带

    缓冲区的流中,则相当于用 write 系统调用函数将这些字符写至相关联的打开文件。

  • 相关阅读:
    JavaScript深入浅出补充——(一)数据类型,表达式和运算符
    Linux简介,虚拟机安装,网络设置,桌面和vim安装
    JavaScript对象之document对象
    python之MySQL学习——数据查询
    python框架Scrapy中crawlSpider的使用
    在Scrapy中使用IP池或用户代理更新版(python3)
    封装IP池和用户代理相应的类(python3)
    在Scrapy中使用IP池或用户代理(python3)
    scrapy工程创建及pycharm运行
    python框架Scrapy报错TypeError: 'float' object is not iterable解决
  • 原文地址:https://www.cnblogs.com/solohac/p/4154171.html
Copyright © 2020-2023  润新知