多线程、多进程写同一日志情况下的日志库中 I/O 的选型

来源:互联网 发布:亚瑟士淘宝假货多吗 编辑:程序博客网 时间:2024/05/17 01:46

文件描述符与 inode 相关背景知识


出自《The Linux Programming Interface》


多线程

有上面的背景知识可知,多线程情况下写同一文件用的是同一个【文件偏移量】,因此只要单条写日志操作是原子操作,就不会出现日志混乱的情况。

系统 I/O

系统 I/O write() 不带应用层缓冲(进程级别缓冲),因此只要保证单条日志操作之调用一次 write() 就可以保证多线程是安全的。


标准 I/O

As an example, the POSIX standard requires that C stdio FILE* operations are atomic.(https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_concurrency.html)

待确认:多个线程是否用同一缓冲区?

先假设是(个人认为是,缓冲区应该是全局变量),全进程共享缓冲区,只要单条写日志操作是原子操作,也不会出现日志混乱。


多进程

标准 I/O

如果用标准 I/O, 不管单条写日志操作是不是原子操作,都会出现日志混乱,因为哪个进程先冲刷缓冲区不确定,更不用说【文件偏移量】不同步的问题了。如果没写一条日志就冲刷一次,又会大大降低性能。


系统 I/O

系统 I/O 的缓冲机制是内核级别的,因此只要保证一下两点,就不会日志混乱:

1. 单条写日志操作是原子操作;

2. 【设置偏移量+write】 是原子操作。


如何做到【设置偏移量+write】 是原子操作呢?

由上图可知,如果 fd 是从父进程继承过来的,【文件偏移量】是共享的,不用人工干预;

如果进程是毫不关联的或者是分别打开的,那么如何解决【文件偏移量】的问题呢?假设每次 【lseek+write】,这种操作不是原子操作,如何保证【设置偏移量+write】 是原子操作呢?open() 提供了一个 O_APPEND 的 FLAG, 每次直接 write() 都是追加到文件末尾,这样【设置偏移量+write】就是一步完成了。(The Linux Programming Interface)

注意:open() 时如果设置了  O_APPEND, write 前 lseek 是无效的。


更新文件偏移量的内核 bug

Among the APIs subsequently listed are write() and writev(2).  And among the effects that should be atomic across threads (and processes) are updates of the file offset.  However, on Linux before version 3.14, this was not the case: if two processes that share an open file description (see open(2)) perform a write() (or writev(2)) at the same time, then the I/O operations were not atomic with respect updating the file offset, with the result that the blocks of data output by the two processes might (incorrectly) overlap.  This problem was fixed in Linux 3.14.(http://man7.org/linux/man-pages/man2/write.2.html)

共享系统级文件描述符时,【更新偏移量+写】不是原子操作,导致即使共享偏移量也会有问题,kernel 3.14 中才修复。


日志滚动(rotate)——以上说的都是错的

在实际应用中,日志是需要滚动的(rotate),即超过配置的大小就要切换文件或者从头开始写,这样单一的写日志操作原子性已经不能使用多线程或多进程的场景了,还是需要加锁。

在日志滚动场景中,以下几步操作是必须的:

1. 检查日志大小是否超过配置值;

2. 切换日志文件名或移动到文件首部。


此外,write(系统调用)、fwrite(标准I/O)并不保证一次写完用户所有数据(几率比较小,通过返回值也可以判断是否写完)。


0 0
原创粉丝点击