我花了几个小时来挖掘行为,首先是关于这些问题:
- "写入(2)"到本地文件系统的原子性
- 如何从两个进程同步 - 使原子 - 写入一个文件?
- 如何以编程方式确定特定文件的"写入"系统调用是否是原子的?
- 如果 2 个不同的进程同时对同一文件调用写入系统调用会发生什么情况
- http://article.gmane.org/gmane.linux.kernel/43445
似乎如果我们在打开文件时使用"O_APPEND"标志,那么在 Linux 上从多个进程记录到同一个文件总是可以的。我相信python肯定在其日志记录模块中使用"O_APPEND"标志。
从一个小测试:
#!/bin/env python
import os
import logging
logger = logging.getLogger('spam_application')
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
logger.addHandler(fh)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
for i in xrange(10000):
p = os.getpid()
logger.debug('Log line number %s in %s', i, p)
我用:
./test.py & ./test.py & ./test.py & ./test.py &
我发现垃圾邮件.log没有任何问题。这种行为可能支持上述结论。
但问题来了:
- 这是什么意思?
- 使用它的场景是什么,仅用于文件轮换?
上进行写入,我的意思是它们在同一个文件上调用 write(2),谁确保来自两个进程的数据不交错(内核还是文件系统?),以及如何。[注意:我只是想深入了解写入系统调用,欢迎对此进行任何点击。
编辑1 :
这个和这个只是为了兼容不同的操作系统环境,如Windows,Linux或Mac?
编辑2 :
再做一个测试,每次将 8KB 字符串提供给 logging.debug。这次我可以看到垃圾邮件.log中的"交错"行为。此行为正是上面一页中对PIPE_BUF指定的。所以在 Linux 上的行为似乎很明显,如果 write(2) 的大小小于 PIPE_BUF,则使用 O_APPEND 是可以的。
越来越深。现在我认为这些事实很清楚:
使用 O_APPEND, 来自多个进程的并行写入(2) 是可以的。只是行的顺序未确定,但行不会交错或相互覆盖。根据Niall Douglas对理解来自多个进程的并发文件写入的回答,数据的大小是任意数量的。我已经在 linux 上"按任何数量"对此进行了测试,但没有找到上限,所以我想这是对的。
没有O_APPEND,这将是一团糟。POSIX 所说的是"此卷 POSIX.1-2008 未指定从多个进程并发写入文件的行为。应用程序应使用某种形式的并发控制。
现在我们进入python。我在 EDIT3 中做的测试,即 8K,我找到了它的起源。Python 的 write() 实际上使用 fwrite(3),我的 python 在这里设置了一个BUFF_SIZE,即 8192。根据 abarnert 在 Linux 上文件的默认缓冲区大小中的回答。这个8192有一个很长的故事。
但是,欢迎提供更多信息。
我不会在这里依赖测试。奇怪的事情只能在竞争条件下发生,通过测试展示竞争条件几乎是无稽之谈,因为竞争条件不太可能发生。因此,它可以很好地用于 1000 次测试运行,并在以后的生产中随机中断......您引用的页面说:
不支持从多个进程记录到单个文件,因为在 Python 中没有标准方法可以跨多个进程序列化对单个文件的访问
这并不意味着它会破裂...它甚至可以在特定文件系统上的特定实现中是安全的。这只是意味着它可以中断,没有任何希望修复任何其他版本的 Python 或任何其他文件系统。
如果你真的想确保它,你必须深入研究Python源代码(针对你的版本)来控制日志记录的实际实现方式,并控制它在你的文件系统上是否安全。而且,您将始终受到以下可能性的威胁:日志记录模块中的后续优化可能会破坏您的假设。
恕我直言,这就是日志记录手册中警告的原因,并且存在一个特殊模块来允许并发日志记录到同一文件。最后一个不依赖于任何未指定的内容,而只是使用显式锁定。
我尝试过类似的代码(我在python 3中尝试过)
import threading
for i in range(0,100000):
t1 = threading.Thread(target= funtion_to_call_logger, args=(i,))
t1.start()
这对我来说完全有效,类似的问题在这里得到解决。
这占用了大量的 CPU 时间,但没有内存。
编辑:
精细表示所有请求的内容都被记录了,但订单丢失了。因此竞争条件仍然不固定,