爲了讓本身的思路更加清晰,下面我都會稱日誌爲 log。由於日誌這個詞有兩種含義,詳情見百度百科釋義或者維基百科釋義。php
咱們這裏說的固然是服務器日誌,也就是 server log 。linux
通常寫入 log 都會遵循如下步驟:nginx
int fd = open(path)
write(fd, sign_append) fclose(fd)
解釋一下上面的代碼:程序員
1. int fd = open(path) 服務器
會經過系統調用打開一個文件描述符,或者在其餘語言中也能夠稱做資源描述符,資源類型,或句柄。app
2. write(fd, append = 1)框架
write 系統調用,並加上 append 標誌,會執行 seek 和 write 兩個系統調用,可是這種系統調用是原子性的。socket
原子性意味着 seek 和 write 會同時執行,不會有兩個線程產生交叉,必須 a 線程執行完 seek 和 write ,b 線程才能繼續執行(這裏說線程,是由於線程纔是 cpu 調度的基本單位)。函數
因此在 nginx 中,咱們加上 append 標誌,就不用對線程上鎖了。this
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 不用加鎖。
若是根據上面的說法,我們能夠設置好 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 文檔也說明了:
因此,最終須要根據不一樣的語言,具體分析。