Python multiprocessing 打日志

一定要打日志。

新手写代码不喜欢打日志,觉得日志配置起来麻烦,又没什么用,所以懒得打日志。配置其实确实挺麻烦的,但是不经历一次联调的时候出了错不知道去哪里找头绪的情况你是不会知道打日志是有多么重要的。反正这次的项目在开启之前,我就打定主意一定要打日志,于是我就打算用 Python 自带的 logging 库来搞定日志。

但是因为我要处理的是计算密集型的任务,所以不能用 Python 的多线程,于是就要上 multiprocessing。还好我在用之前机制地去查了一下 logging 里的 handler 是不是支持并发。文档给出的答案是:支持并发,不过仅仅是在 threading 里支持,在 multiprocessing 里不支持。以下说明出自 官方文档

Although logging is thread-safe, and logging to a single file from multiple threads in a single process is supported, logging to a single file from multiple processes is not supported, because there is no standard way to serialize access to a single file across multiple processes in Python.

但是其实如果是 Linux ,FileHandler 的行为是可以保证顺序写入的,不过要求你的单条日志大小不超过 Linux 中管道缓冲区的大小……而这个缓冲区大小要你自己改,而且不能超过系统设置中的最大大小,这是在 StackOverflow 上查到的。

解决方案

第一种,用 Python 提供的 SocketHandler 把所有的日志通过 Socket 发送到另一个专门用来写日志的线程/进程,那个进程实现一个 Socket Server 来接收日志。可以用 Python 提供的 Pickle 库来搞定序列化和反序列化,或者直接发 json 之类的也行。

如果用这个方法,日志默认是走系统的 TCP/IP 协议栈的,所以可以试试 Unix Domain Socket,这个东西是不走协议栈的,会比默认的快一些,但是你要自己继承一下默认的 SocketHandler 这个类,然后覆盖它的 makeSocket 函数,返回一个你想要的类型的 Socket。具体可以看看 官方文档

第二种,用 multiprocessing.Queue 这个跨进程的队列来搞定 IPC。这个队列是进程安全的,所以不用担心写坏了,然后再像上面那样写一个专门的线程/进程来搞定写入日志的事情。

开源解决方案

先说一个鼻祖:ConcurrentLogHandler 。提供了最基本的多进程日志写入的功能,其他的功能一概没有。所以系统甚至会被磁盘 I/O 阻塞住,然后 CPU 都跑不满的。

后辈就做得比较好了。比如这个 concurrent-log-handler,它用了一个队列来缓存待写入的日志,所以应该是不会因为磁盘 I/O 太慢而跑不满 CPU 了。

一些思考

Python 这个东西开发速度是快,但是写了这一段时间的 Python 之后,我感觉其实这个语言并不是非常优秀。不是说这个语言不优秀,而是它的官方解释器不优秀。如果有一天 PyPy 能够支持大多数的库,多线程模型能够继续在多核 CPU 上发扬光大,那么我想 Python 会有更多人使用。虽然 multiprocessing 暂缓了它的多线程无法利用多核 CPU 优势的情况,但是跨进程共享对象还是一个比较难以用一种优雅的方法解决的问题。

最近准备学一学 Java 或者 Golang,具体还没想好。希望自己还能有那么多精力去进行一些个人拓展吧。

CC BY-NC-SA 4.0 Python multiprocessing 打日志 by James & Alice is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据