Python - добавление в один файл из нескольких потоков
Я пишу приложение, которое добавляет строки в один файл из нескольких потоков.
У меня есть проблема, в которой некоторые строки добавляются без новой строки.
Любое решение для этого?
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()
Ответ 2
тот факт, что вы никогда не видите перепутанного текста в одной строке или новых строк в середине строки, является ключом, который вам фактически не нужно синхронизировать с добавлением к файлу. проблема заключается в том, что вы используете печать для записи в один дескриптор файла. Я подозреваю, что print
на самом деле выполняет 2 операции с дескриптором файла за один вызов, и эти операции движутся между потоками. в основном print
делает что-то вроде:
file_handle.write('whatever_text_you_pass_it')
file_handle.write(os.linesep)
и потому, что разные потоки делают это одновременно в одном и том же дескрипторе файла, иногда один поток будет попадать в первую запись, а другой поток будет получать первую запись, а затем вы получите два возврата каретки в строке. или действительно любая их перестановка.
Самый простой способ обойти это - прекратить использование print
и просто использовать write
напрямую. попробуйте что-то вроде этого:
output.write(f + os.linesep)
это все еще кажется мне опасным. im не уверен, что gaurantees вы можете ожидать со всеми потоками, используя тот же объект дескриптора файла и борясь за свой внутренний буфер. лично id side step the all issue и просто каждый поток получает свой собственный дескриптор файла. также обратите внимание, что это работает, потому что по умолчанию для сброса буфера записи буферизируется строка, поэтому, когда он выполняет сброс в файл, он заканчивается на os.linesep
. чтобы заставить его использовать буферизацию по строке, отправьте a 1
в качестве третьего аргумента open
. вы можете проверить это следующим образом:
#!/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
Ответ 3
А может быть еще несколько строк, где их не должно быть?
Вы должны иметь в виду тот факт, что доступ к общему ресурсу не должен осуществляться более чем одним потоком одновременно, иначе могут произойти непредсказуемые последствия (это называется "атомарными операциями" при использовании потоков).
Взгляните на эту страницу для небольшой интуиции: механизмы синхронизации потоков в Python