J'écris une application qui ajoute des lignes au même fichier à partir de plusieurs threads.
J'ai un problème dans lequel certaines lignes sont ajoutées sans nouvelle ligne.
Une solution pour ça?
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()
La solution consiste à écrire dans le fichier dans un seul thread.
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()
le fait que vous ne voyez jamais de texte brouillé sur la même ligne ou de nouvelles lignes au milieu d'une ligne est un indice que vous n'avez en fait pas besoin de synchroniser en ajoutant au fichier. le problème est que vous utilisez l'impression pour écrire dans un descripteur de fichier unique. je soupçonne que print
fait en fait 2 opérations sur le descripteur de fichier en un seul appel et que ces opérations font la course entre les threads. en gros, print
fait quelque chose comme:
file_handle.write('whatever_text_you_pass_it')
file_handle.write(os.linesep)
et parce que différents threads le font simultanément sur le même descripteur de fichier, parfois un thread obtiendra la première écriture et l'autre thread obtiendra alors sa première écriture et vous obtiendrez ensuite deux retours chariot consécutifs. ou vraiment toute permutation de ceux-ci.
le moyen le plus simple de contourner ce problème est d'arrêter d'utiliser print
et d'utiliser simplement write
directement. essayez quelque chose comme ceci:
output.write(f + os.linesep)
cela me semble toujours dangereux. Je ne sais pas quelles garanties vous pouvez attendre avec tous les threads utilisant le même objet descripteur de fichier et rivalisant pour son tampon interne. personnellement, côté id, résoudre le problème et faire en sorte que chaque thread obtienne son propre descripteur de fichier. notez également que cela fonctionne parce que la valeur par défaut pour les vidages de tampon d'écriture est mise en mémoire tampon de ligne, donc quand il fait un vidage du fichier, il se termine sur un os.linesep
. pour le forcer à utiliser le tampon de ligne, envoyez un 1
comme troisième argument de open
. vous pouvez le tester comme ceci:
#!/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)
La sortie ressemble à ceci:
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
Et peut-être quelques nouvelles lignes où elles ne devraient pas être?
Vous devez avoir à l'esprit le fait qu'une ressource partagée ne doit pas être accessible par plus d'un thread à la fois, sinon des conséquences imprévisibles pourraient se produire (cela s'appelle utiliser des `` opérations atomiques '' tout en utilisant des threads).
Jetez un oeil à cette page pour une petite intuition: Thread Synchronization Mechanisms in Python