python多进程读写文件_Python多进程写文件时的一些探究

问题提出

在没有并发控制的情况下,Python多进程向同一个文件写数据(限制单次写入数据大小)是安全的吗?

这里的安全是指:

不会有进程的日志丢失(被覆盖)

两次写入的数据不会相互混着输出(譬如A进程单次写入aaaa,B进程写入bbbb,最后的日志不会出现aaababbb)

测试

首先,我做了四个测试,测试代码如下(test.py):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107# -*- coding: utf-8 -*-

import os

import sys

import time

class TestBase(object):

buffering = -1

test_filename = None

f = None

@classmethod

def init_file(cls):

assert cls.test_filename is not None

cls.f = open(cls.test_filename, "w", buffering=cls.buffering)

class Test1(TestBase):

"""测试一

* buffering参数默认

* 无自定义buffer控制

* 文件在进程fork前打开

"""

test_filename = 'file1.tmp'

@classmethod

def run(cls, char, process):

start_time = time.time()

chars = "%s\n" % (char * 10)

for _ in range(int(1e5)):

cls.f.write(chars)

cls.f.close()

print "%s consume %ss" % (process, time.time() - start_time)

class Test2(Test1):

"""测试二

* 设置buffering参数

* 无自定义buffer控制

* 文件在进程fork前打开

"""

buffering = 1024

test_filename = 'file2.tmp'

class Test3(TestBase):

"""测试三

* 设置buffering参数

* 自定义buffer控制

* 文件在进程fork前打开

"""

buffering = 1024

test_filename = 'file3.tmp'

@classmethod

def run(cls, char, process):

buffer_size = cls.buffering

start_time = time.time()

chars = "%s\n" % (char * 10)

buffer_used = 0

for _ in range(int(1e5)):

if buffer_used + len(chars) >= buffer_size:

cls.f.flush()

buffer_used = 0

cls.f.write(chars)

buffer_used += len(chars)

cls.f.close()

print "%s consume %ss" % (process, time.time() - start_time)

class Test4(Test3):

"""测试四

* 设置buffering参数

* 自定义buffer控制

* 文件在进程fork后打开

"""

test_filename = 'file4.tmp'

if __name__ == '__main__':

test_num = int(sys.argv[1])

test_class_map = {

1: Test1,

2: Test2,

3: Test3,

4: Test4,

}

test_class = test_class_map.get(test_num)

if not test_class:

sys.stderr.write("unknown test_num")

sys.exit(1)

# 前3个测试,在fork前打开文件

if test_num <= 3:

test_class.init_file()

if os.fork() == 0: # child process

char = 'a'

process = 'child'

else: # parent process

char = 'b'

process = 'parent'

# 第四个测试,在fork后打开文件

if test_num == 4:

test_class.init_file()

test_class.run(char, process)

在都是利用os.fork实现多进程(在Unix下,multiprocessing库默认也是使用os.fork)写一个文件的情况下,四个测试主要有以下不同:

在调用Python内建函数open时,是否设置buffering,默认是-1(也是Python默认值)

是否有自定义buffering控制。这里的自定义buffering控制是指,上层代码检测在进行此次写入时,是否会造成缓冲区溢出(大于buffer_size),如果溢出,先进行flush操作,再进行write操作

文件是在fork前还是之后打开

分别执行

1

2

3

4python test.py 1

python test.py 2

python test.py 3

python test.py 4

得出结果如下:

测试环境:MAC OS X

测试

Python 2.7.13

Python 3.6.2

Test1

无丢失发生,数据混着输出

正常

Test2

无丢失发生,数据混着输出

正常

Test3

正常

正常

Test4

数据丢失一半

数据丢失一半

结果分析

为什么Python 2和Python 3的结果不一样呢?

Python 3使用了New I/O(PEP 3116)。而一个重要的变化是,Python 3在自己这一层实现了buffer机制。Python 2写文件使用的是C库函数fwrite,而buffer机制完全依靠fwrite。Python 3使用的是write系统调用,buffer机制是自己实现的

为什么在Python 2中,Test3是正常的呢?

这个推断是在buffer满时,fwrite的处理是不能保证多进程安全的。而在上层代码强制flush的情况下,能保证数据正常写入文件

为什么在fork之后打开文件时,数据会丢失呢?

这个和fork操作对每个文件的current stream position(即调用tell方法返回的)的处理有关,参考这里

其他

Nginx的ngx_http_log_module模块的I/O处理

根据文档,说明了在启用buffering时,写入文件的时机:

When buffering is enabled, the data will be written to the file:

if the next log line does not fit into the buffer;

if the buffered data is older than specified by the flush parameter (1.3.10, 1.2.7);

when a worker process is re-opening log files or is shutting down.

根据这儿的源代码,Nginx会根据NGX_HAVE_PWRITE宏的设置,使用pwrite或者write

关于fwrite,write,pwrite的一些讨论:

引用1

Atomic/non-atomic: A write is atomic if the whole amount written in one operation is not interleaved with data from any other process. This is useful when there are multiple writers sending data to a single reader. Applications need to know how large a write request can be expected to be performed atomically. This maximum is called {PIPE_BUF}. This volume of IEEE Std 1003.1-2001 does not say whether write requests for more than {PIPE_BUF} bytes are atomic, but requires that writes of {PIPE_BUF} or fewer bytes shall be atomic.

及根据2,可知在写入少于{PIPE_BUF}bytes的数据时,write及pwrite系统调用应该是原子的

而fwrite相对于write/pwrite有内置buffering,在没有自定义buffering的情况下,能很大的提升写文件的性能

Python的logging模块

logging模块的FileHandler也是采用的open函数,在多进程情况下,也符合以上讨论

Java的I/O处理The new I/O spec is intended to be similar to the Java I/O libraries, but generally less confusing.

看来Java对I/O的处理更加类似Python 3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值