複数のスレッドから同じファイルに行を追加するアプリを書いています。
一部の行が改行なしで追加されるという問題があります。
これに対する解決策はありますか?
class PathThread(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def printfiles(self, p):
for path, dirs, files in os.walk(p):
for f in files:
print(f, file=output)
def run(self):
while True:
path = self.queue.get()
self.printfiles(path)
self.queue.task_done()
pathqueue = Queue.Queue()
paths = getThisFromSomeWhere()
output = codecs.open('file', 'a')
# spawn threads
for i in range(0, 5):
t = PathThread(pathqueue)
t.setDaemon(True)
t.start()
# add paths to queue
for path in paths:
pathqueue.put(path)
# wait for queue to get empty
pathqueue.join()
解決策は、1つのスレッドのみでファイルに書き込むことです。
import Queue # or queue in Python 3
import threading
class PrintThread(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def printfiles(self, p):
for path, dirs, files in os.walk(p):
for f in files:
print(f, file=output)
def run(self):
while True:
result = self.queue.get()
self.printfiles(result)
self.queue.task_done()
class ProcessThread(threading.Thread):
def __init__(self, in_queue, out_queue):
threading.Thread.__init__(self)
self.in_queue = in_queue
self.out_queue = out_queue
def run(self):
while True:
path = self.in_queue.get()
result = self.process(path)
self.out_queue.put(result)
self.in_queue.task_done()
def process(self, path):
# Do the processing job here
pathqueue = Queue.Queue()
resultqueue = Queue.Queue()
paths = getThisFromSomeWhere()
output = codecs.open('file', 'a')
# spawn threads to process
for i in range(0, 5):
t = ProcessThread(pathqueue, resultqueue)
t.setDaemon(True)
t.start()
# spawn threads to print
t = PrintThread(resultqueue)
t.setDaemon(True)
t.start()
# add paths to queue
for path in paths:
pathqueue.put(path)
# wait for queue to get empty
pathqueue.join()
resultqueue.join()
同じ行または行の途中の新しい行に乱雑なテキストが表示されないという事実は、実際にファイルへの追加を同期する必要がない手がかりです。問題は、printを使用して単一のファイルハンドルに書き込むことです。 print
は実際には1回の呼び出しでファイルハンドルに対して2つの操作を実行しており、それらの操作はスレッド間で競合しています。基本的にprint
は次のようなことをしています:
file_handle.write('whatever_text_you_pass_it')
file_handle.write(os.linesep)
異なるスレッドが同じファイルハンドルでこれを同時に実行しているため、1つのスレッドが最初の書き込みを取得し、他のスレッドが最初の書き込みを取得し、2つのキャリッジリターンが連続して取得される場合があります。または本当にこれらの順列。
これを回避する最も簡単な方法は、print
の使用を停止し、write
を直接使用することです。このようなものを試してください:
output.write(f + os.linesep)
これはまだ危険なようです。同じファイルハンドルオブジェクトを使用し、その内部バッファーをめぐって競合するすべてのスレッドでどのような保証が期待できるかわかりません。個人的にidで問題全体を回避し、すべてのスレッドに独自のファイルハンドルを取得させます。また、書き込みバッファフラッシュのデフォルトはラインバッファリングであるため、これが機能することにも注意してください。ファイルへのフラッシュを実行すると、os.linesep
で終了します。ラインバッファーを使用するように強制するには、open
の3番目の引数として1
を送信します。次のようにテストできます。
#!/usr/bin/env python
import os
import sys
import threading
def hello(file_name, message, count):
with open(file_name, 'a', 1) as f:
for i in range(0, count):
f.write(message + os.linesep)
if __name__ == '__main__':
#start a file
with open('some.txt', 'w') as f:
f.write('this is the beginning' + os.linesep)
#make 10 threads write a million lines to the same file at the same time
threads = []
for i in range(0, 10):
threads.append(threading.Thread(target=hello, args=('some.txt', 'hey im thread %d' % i, 1000000)))
threads[-1].start()
for t in threads:
t.join()
#check what the heck the file had
uniq_lines = set()
with open('some.txt', 'r') as f:
for l in f:
uniq_lines.add(l)
for u in uniq_lines:
sys.stdout.write(u)
出力は次のようになります。
hey im thread 6
hey im thread 7
hey im thread 9
hey im thread 8
hey im thread 3
this is the beginning
hey im thread 5
hey im thread 4
hey im thread 1
hey im thread 0
hey im thread 2
そして、多分それらはあるべきではないいくつかの改行?
共有リソースには一度に複数のスレッドがアクセスしないようにしてください。そうしないと、予期しない結果が発生する可能性があります(スレッドの使用中に「アトミック操作」を使用して呼び出されます)。
少し直感的にこのページを見てください: Pythonのスレッド同期メカニズム