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

程序写日志文件时该不该加锁

日志(log)

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

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

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

写入 log

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

int fd = open(path)write(fd, sign_append)fclose(fd)

解释一下上面的代码:

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 代表可以打开的文件数量):

$ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedscheduling priority (-e) 0file size (blocks, -f) unlimitedpending signals (-i) 15732max locked memory (kbytes, -l) 64max memory size (kbytes, -m) unlimitedopen files (-n) 1024pipe size (512 bytes, -p) 8POSIX message queues (bytes, -q) 819200real-time priority (-r) 0stack size (kbytes, -s) 8192cpu time (seconds, -t) unlimitedmax user processes (-u) 15732virtual memory (kbytes, -v) unlimitedfile locks (-x) unlimited

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

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

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

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

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

跟进源码(非 php 程序员或者对 php 底层源码无兴趣的可以跳过了):

file_put_contents 底层实现:

// file.c/* {{{ proto int|false file_put_contents(string file, mixed data [, int flags [, resource context]])   Write/Create a file with contents data and return the number of bytes written */PHP_FUNCTION(file_put_contents){...case IS_STRING:   if (Z_STRLEN_P(data)) {      numbytes = php_stream_write(stream, Z_STRVAL_P(data), Z_STRLEN_P(data));      if (numbytes != Z_STRLEN_P(data)) {         php_error_docref(NULL, E_WARNING, "Only %zd of %zd bytes written, possibly out of free disk space", numbytes, Z_STRLEN_P(data));         numbytes = -1;      }   }   break;...}// php_streams.hPHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t count);#define php_stream_write_string(stream, str)   _php_stream_write(stream, str, strlen(str))#define php_stream_write(stream, buf, count)   _php_stream_write(stream, (buf), (count))// streams.cPHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t count){  ...   if (stream->writefilters.head) {      bytes = _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL);   } else {      bytes = _php_stream_write_buffer(stream, buf, count);   }   if (bytes) {      stream->flags |= PHP_STREAM_FLAG_WAS_WRITTEN;   }   return bytes;}/* Writes a buffer directly to a stream, using multiple of the chunk size */static ssize_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count){...while (count > 0) {   ssize_t justwrote = stream->ops->write(stream, buf, count);   if (justwrote <= 0) {      /* If we already successfully wrote some bytes and a write error occurred       * later, report the successfully written bytes. */      if (didwrite == 0) {         return justwrote;      }      return didwrite;   }   buf += justwrote;   count -= justwrote;   didwrite += justwrote;   /* Only screw with the buffer if we can seek, otherwise we lose data    * buffered from fifos and sockets */   if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {      stream->position += justwrote;   }}}// php_streams.h/* operations on streams that are file-handles */typedef struct _php_stream_ops  {   /* stdio like functions - these are mandatory! */   ssize_t (*write)(php_stream *stream, const char *buf, size_t count);   ssize_t (*read)(php_stream *stream, char *buf, size_t count);   int    (*close)(php_stream *stream, int close_handle);   int    (*flush)(php_stream *stream);   const char *label; /* label for this ops structure */   /* these are optional */   int (*seek)(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset);   int (*cast)(php_stream *stream, int castas, void **ret);   int (*stat)(php_stream *stream, php_stream_statbuf *ssb);   int (*set_option)(php_stream *stream, int option, int value, void *ptrparam);} php_stream_ops; // plain_wrapper.cstatic ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count){   php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;   assert(data != NULL);   if (data->fd >= 0) {#ifdef PHP_WIN32      ssize_t bytes_written;      if (ZEND_SIZE_T_UINT_OVFL(count)) {         count = UINT_MAX;      }      bytes_written = _write(data->fd, buf, (unsigned int)count);#else      ssize_t bytes_written = write(data->fd, buf, count);#endif      if (bytes_written < 0) {         if (errno == EWOULDBLOCK || errno == EAGAIN) {            return 0;         }         if (errno == EINTR) {            /* TODO: Should this be treated as a proper error or not? */            return bytes_written;         }         php_error_docref(NULL, E_NOTICE, "write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno));      }      return bytes_written;   } else {#if HAVE_FLUSHIO      if (data->is_seekable && data->last_op == 'r') {         zend_fseek(data->file, 0, SEEK_CUR);      }      data->last_op = 'w';#endif      return (ssize_t) fwrite(buf, 1, count, data->file);   }}

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

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

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

而且,php 文档也说明了:

系统调用跟进法 (2020年03月18日更新)

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

<?phpfile_put_contents('a.log', str_pad('111', 8192, '-'));file_put_contents('a.log', str_pad('111', 8199, '-'));

shell:

$ sudo strace php file_put.php

输出如下

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

(0)

相关推荐

  • Thinkphp5+Redis实现商品秒杀

    环境:wamp,redis 要求:安装WAMP,Redis,以及为PHP安装Redis扩展(怎么安装Redis可以看看我前面写的文章) 秒杀功能大致思路:获取缓存列表的长度,如果长度(llen)等于0 ...

  • 【精品博文】Itop4412学习笔记(2)

    今天学习的是文件IO的操作,需要记录的点: 1.库函数头文件 在所有Linux系统中,对文件的操作都只需包含下面四个头文件即可: #include <unistd.h>     #incl ...

  • Windows删除文件时提示“文件正被另一个应用程序使用”

    在日常使用中,我们在删除文件的时候可能会遇到"文件正在被另一程序使用"的提示,那么怎么找到是哪个程序在使用这个文件呢? 接下来,教你怎么找到这个程序. 首先,记住你要删除的文件夹或 ...

  • 我们84岁时,能像文徵明这样写小楷么?

    文徵明 文徵明小楷<盘谷叙>,纸本长卷:纵15.8厘米,横129.7厘米,藏于中国台北故宫博物院.该小楷作于公元1554年,时文征明已耄耋之年84岁高龄.现在人到了84岁也是长寿高龄老人了 ...

  • 写字时把这三个要素写进去,书法就是好作品,你不想尝试吗?

    原创太一智慧书画艺术2021-01-26 10:27:42 有时候,书法爱好者只会临摹,在遇到创作时就不会写字了,这是一个正常的现象.为什么会出现这样的问题?原因大致有三个:一是长时间临摹,字帖中的字 ...

  • 写借条时,借条上这个字写错,一分钱都拿不回来,看完转告家人

    写借条时,借条上这个字写错,一分钱都拿不回来,看完转告家人 写借条时,借条上这个字写错,一分钱都拿不回来,看完转告家人 展开

  • win7如何防止复制大文件时出现崩溃

    在使用win7系统的时候总是会遇到一些问题,比如在复制大文件的过程中突然出现系统崩溃,导致复制过程无法继续,那么win7如何防止复制大文件时出现崩溃呢?就此问题,小编就给大家分享一下win7防止复制大 ...

  • 如何从Apache日志文件生成完整的访问者计数

    我描述了如何从Apache日志文件创建报告,以了解本地主机与其他主机之间的匹配次数.只需将IP地址替换为另一个地址,即可轻松更改该脚本,以提供针对任何单个IP地址(相对于世界其他地方)的报告. 也可以 ...

  • Win7系统关闭日志文件的方法(图文)

    Win7系统运行一段时间后,会产生大量的日志文件,用垃圾清除类的软件能发现没用的日志文件,没用的日志文件反而占用内存空间.其实大家可以关闭日志文件,那么Win7系统怎么关闭日志文件?如果不知道具体操作 ...

  • 删除文件时,找不到该项目!

    Windows10 删除文件时,找不到该项目! 解决办法: 1. 新建记事本,输入如下内容: DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1 2. 另存为"强行删除. ...

  • 有了这张图再也不怕把标题写错,写标题的时...

    有了这张图再也不怕把标题写错,写标题的时候,我们经常会犯各种错误,如关于某某意见的函,正确写法是关于某某的函,应该把意见删掉:如关于对某某的批复,正确写法是关于某某的批复,应该把对删掉等.#职场干货# ...