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

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

日志(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 -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 <= 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.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 < 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 为 8096 字节,分别进行 write。

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

而且,php 文档也说明了:

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

原文地址:https://www.cnblogs.com/wudanyang/p/12113348.html

时间: 2024-07-30 09:05:47

程序写日志文件时该不该加锁的相关文章

ASP.NET Core 开发-Logging 使用NLog 写日志文件

ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 .NET Core 和 ASP.NET Core . ASP.NET Core已经内置了日志支持,可以轻松输出到控制台. 学习Logging 组件的相关使用,使用NLog 将日志写入到文件记录. Logging 使用 新建一个 ASP.NET Core 项目,为了方便,我选择Web 应用程序,改身份验证 改为 不进行身份验证. 新建好以后,会自动引用好对应的 类库.这样我们就可以直接使用 Logge

Log4j写日志文件使用详解

Log4j输出到控制台成功,写入文件失败 - Log4j和commons log的整合 一.今天在使用commongs-logging.jar和log4j.properties来输出系统日志的时候,发现日志能够成功的输出到控制台,但是去不能写到目的文件中,具体的步骤和原因如下: 1. 只在项目中引入commons-logging.jar commons-logging.jar 使用 Log logger = LogFactory.getLogger(XXX.class), 如果有log4j.pr

C# 简单的往txt中写日志,调试时很有用

原文 http://blog.csdn.net/hejialin666/article/details/6106648 有些程序在调试时很难抓住断点(如服务程序),有些程序需要循环无数次,要看每一次或某一次的结果,等等吧! 那就来个简单的写日志程序吧,txt文件生成在debug目录里 using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Windows.Fo

C#写UTF8文件时指定是否含BOM头

BOM的基本概念 在UCS 编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF.而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中.UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE".这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的:如果收到FFFE,就表明这个字节流是Little-Endian的.因此字符"ZERO WIDTH N

写csv文件时遇到的错误

1.错误 在许多文件中,写入csv文件时都加"wb",w指写入,b指二进制 如: csvwrite=csv.writer(open("output.csv","wb")) Temp=["row1","row2","row3",] csvwrite.writerow(Temp) 或者是 #!/usr/bin/env python #coding:utf-8 import csv #csv写

修改winform安装包写日志文件权限

1.如果程序有写入文件的功能要添加该文件 并配置该属性 改成这个即可 原文地址:https://www.cnblogs.com/teng-0802/p/11776095.html

java程序读取资源文件时路径如何指定

java程序,读取资源操作有两种方式. 一是直接通过File进行的.例如FileReader.BufferedReader等.文件目录就是相对于Project了.如我新建一个MyProject,那么,我在MyProject下有一个icons文件夹,如果想读取icons里面的东西,路径是诸如"icons/***.gif"的相对路径就可以了. 二是通过getClass().getResource("")得到.第二种方法必须保证文件是在classpath包括的路径下.而一

c++写日志文件的操作

1 class LogFile 2 { 3 public: 4 static LogFile &instance(); 5 operator FILE *() const { return m_file; } 6 private: 7 LogFile(const char *filename) 8 { 9 m_file = fopen(filename, "a+"); 10 } 11 ~LogFile() 12 { 13 fclose(m_file); 14 } 15 FILE

一种多线程写日志文件的解决方案 c#源代码演示

using System;using System.Collections.Generic;using System.Collections;using System.Text;using System.IO;using System.Timers; namespace ComUtil{    public class LogWriter    {        private LogWriter()        {            logtimer.Stop();