web-dev-qa-db-fra.com

Python - ajout au même fichier à partir de plusieurs threads

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()
26
user1251654

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()
33
Kien Truong

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
4
Kevin Kreiser

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

1
SpiXel