天天看点

程序写日志文件时该不该加锁 & PHP 写日志为什么加锁

1. 日志(log)

为了让自己的思路更加清晰,下面我都会称日志为 log。因为日志这个词有两种含义,详情见百度百科释义或者维基百科释义。

日记的另一种说法。“志”字本身为“记录”的意思,日志就为每日的记录(通常是跟作者有关的)。 服务器日志(server log),记录服务器等电脑设备或软件的运作。

我们这里说的当然是服务器日志,也就是  server log 。

2. 写入 log

一般写入 log 都会遵循以下步骤:

解释一下上面的代码:

1. int fd = open(path) 

会通过系统调用打开一个文件描述符,或者在其他语言中也可以称作资源描述符,资源类型,或句柄。

2. write(fd, append = 1)

write 系统调用,并加上 append 标志,会执行 seek 和 write 两个系统调用,但是这种系统调用是原子性的。

原子性意味着 seek 和 write 会同时执行,不会有两个线程产生交叉,必须 a 线程执行完 seek 和 write ,b 线程才能继续执行(这里说线程,是因为线程才是 cpu 调度的基本单位)。

所以在 nginx 中,我们加上 append 标志,就不用对线程上锁了。

3. fclose(fd)

关闭描述符。

linux 一般对打开的文件描述符有一个最大数量的限制,如果不关闭描述符,很有可能造成大 bug。

查看 linux 中限制的方法如下(其中 open files 代表可以打开的文件数量):

程序写日志文件时该不该加锁 & PHP 写日志为什么加锁

所以,如果是系统调用,那么 append 不用加锁。

3. 为什么 php 语言写日志时用了 append 也要加锁?

如果根据上面的说法,咱们可以设置好 write 的 append 标志,然后就可以睡大觉去了,文件永远不会冲突。

但是(一般都有个但是)你去看 php 的框架中都会在 file_put_contents 的 append 之前加锁。

于是,怀疑是因为 file_put_contents 的底层实现没有实现原子性。

file_put_contents 底层实现:

程序写日志文件时该不该加锁 & PHP 写日志为什么加锁
程序写日志文件时该不该加锁 & PHP 写日志为什么加锁

View Code

这个函数最终调用的是函数 php_stdiop_write 

函数 _php_stream_write_buffer 中会将字符串分成多个 chunksize ,每个 chunksize 为 8192 (8K) 字节,分别进行 write。

如果不加锁,那么超过 8192 字节之后,多个进程写日志就会出现混乱。

而且,php 文档也说明了:

程序写日志文件时该不该加锁 & PHP 写日志为什么加锁
程序写日志文件时该不该加锁 & PHP 写日志为什么加锁

最近看到了另外一种防止多次系统调用的方法:使用 stream_set_chunk_size 函数进行控制一次写入的 chunksize

程序写日志文件时该不该加锁 & PHP 写日志为什么加锁

可以看到,写入的时候现在只有一个系统调用了

因为已经知道了 file_put_contents 会将内容分成 8192 字节的数据,所以我们跟进一下 php 在做系统调用的时候,是怎么做的,贴一下测试代码

shell:

 输出如下

程序写日志文件时该不该加锁 & PHP 写日志为什么加锁

 所以,最终需要根据不同的语言,具体分析。

php