谈一下程序写日志文件时加锁问题

生活中的日志是记录你生活的点点滴滴,让它把你内心的世界表露出来,更好的诠释自己的内心世界,而电脑里的日志可以是有价值的信息宝库,也可以是毫无价值的数据泥潭。

日志(log)

为了让自己的思路更加清晰,下面我都会称日志为 log。因为日志这个词有两种含义。

日记的另一种说法。“志”字本身为“记录”的意思,日志就为每日的记录(通常是跟作者有关的)。

服务器日志(server log),记录服务器等电脑设备或软件的运作。

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

写入 log

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

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

解释一下上面的代码:

int fd = open(path)

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

write(fd, append = 1)

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

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

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

fclose(fd)

关闭描述符。

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

查看linux 中限制的方法

ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15732
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15732
virtual memory (kbytes, -v) unlimited
file 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.h
PHPAPI 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.c
PHPAPI 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 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.c
static 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 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 文档也说明了:

论加锁否:程序日志论加锁否:程序日志

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/220356.html<

(0)
管理的头像管理
上一篇2025-04-14 13:04
下一篇 2025-04-14 13:05

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注