Große Daten im Hintergrund verschieben: Ist Threading hier richtig?

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Große Daten im Hintergrund verschieben: Ist Threading hier richtig?

    Ich habe ein "kleines" Programm (Im Kern: drei TableViews und ne Toolbar) mit dem ich meine Daten/Dateien ein wenig sortiere. Funktioniert soweit recht brauchbar und ich bin zufrieden. Werden Daten im Programm hinzugefügt oder gelöscht so werden die zugehörigen Dateien schlicht in den Programmordner/Papierkorb verschoben.
    Letzte Woche habe ich dann ein größeres Datenpaket (im GB-Bereich) verschoben und mein Programm "hing" für ein paar Sekunden. Das war zwar nicht schlimm aber ich fand dass das besser gehen müsste und hab mich mal ans Threading gewagt. Tatsächlich fand/find ich auch das sehr einfach für mein "Problem".

    Nun kam aber in mir eben auch jene Frage im Titel in den Sinn: Ist Threading hier richtig oder wäre die Queue nicht doch die bessere Wahl oder noch was ganz anderes? Und welche Möglichkeiten kann ich nutzen da mein Programm von Mac OS 10.4 bis 10.6 auf jeden Fall laufen muss (setze es auf verschiedenen Rechnern bei mir ein).

    Wie entscheide ich, welche Art der Nebenläufigkeit für meine Anwendung die Richtige ist. Wenn ich das richtig bisher gelesen habe gibts sogar für die Runloop verschiedene Modi, war mir bis letzte Woche so auch nicht bewusst/bekannt. Hat da jemand Tipps für mich? Ich hab jetzt nicht wirklich ein konkretes Problem vorliegen, ich will mir das ganze nur mal anschaun und ich bin Hobby-Programmierer, mache das nicht Hauptberuflich. Wenn möglich sollte die Quelle also auch für Semiprofessionelle verständlich sein ohne gefühlt 231 Bücher zu lesen (was mich jetzt aber auch nicht wirklich abschrecken würde)
    [self setSignature:null];
    [[self postCount] increment];
  • Eine Frage schon mal geklärt, bei mir geht nur Threading weil ich eben 10.4 noch hab, danke gritsch.
    ​Bleibt noch das Generelle (ohne Blick aufs OS): Wie entscheide ich welche Nebenläufigkeit für meine Anwendung sinnvoll ist. Mir fehlt da so ein wenig der Hintergrund wie man das entscheidet.
    [self setSignature:null];
    [[self postCount] increment];
  • Hab mir jetzt noch das Video von der Macoun angeschaut von Amin übers Thema Nebenläufigkeit. Das fand ich für den Einstieg ganz spannend aber wie immer würde ich gern noch ein wenig mehr darüber erfahren. Also jetzt übers Thema Nebenläufigkeit, nicht wie Amin versucht Boris Becker zu imitieren :D
    Inzwischen denke ich sogar, das die Queue für meine Aufgabe besser geeignet wäre. Grund hierfür: Während des Verschiebens sollen sich die Daten ja nicht verändern. Beim Threading müsste ich dann einen Lock drauf setzen, bei der Queue sitzt das Lock ja quasi schon mit drauf.
    Wer Tipps hat, wo ich mich weiter schlau machen kann, nur her damit. Hinweis dazu, meine aktuelle Lektüre: Threading Programming Guide
    [self setSignature:null];
    [[self postCount] increment];
  • Mike schrieb:

    Hab mir jetzt noch das Video von der Macoun angeschaut von Amin übers Thema Nebenläufigkeit. Das fand ich für den Einstieg ganz spannend aber wie immer würde ich gern noch ein wenig mehr darüber erfahren. Also jetzt übers Thema Nebenläufigkeit, nicht wie Amin versucht Boris Becker zu imitieren :D
    Inzwischen denke ich sogar, das die Queue für meine Aufgabe besser geeignet wäre. Grund hierfür: Während des Verschiebens sollen sich die Daten ja nicht verändern. Beim Threading müsste ich dann einen Lock drauf setzen, bei der Queue sitzt das Lock ja quasi schon mit drauf.
    Wer Tipps hat, wo ich mich weiter schlau machen kann, nur her damit. Hinweis dazu, meine aktuelle Lektüre: Threading Programming Guide


    verstehe nicht was du mit "Während des Verschiebens sollen sich die Daten ja nicht verändern." meinst...
  • Mike schrieb:

    Nunja, was passiert z.B. wenn ich eine Datei verschiebe und gleichzeitig ihren Namen ändere. Ich denke sowas wird knallen und ich muss eben verhindern, dass das überhaupt passiert.

    Wenn du aus mehreren Threads gleichzeitig auf die gleichen Daten zugreifst knallt es gegebenenfalls (z.B. ein Thread löscht und der andere ist gerade noch am kopieren des gleichen Datensatzes). In solchen Fällen musst du dann die Threads synchronisieren (z.B.lock setzen etc.) Dieses Problem scheinst du aber mit deinem Anwendungsfall gar nicht zu haben. Du möchtest ja nur erreichen das die GUI nicht einfriert beim kopieren größerer Datensätze. Du könntest dann einfach einen Fortschrittsbalken anzeigen, welche die restliche GUI blockiert, sodass der Anwender die zu kopierenden Daten nicht bearbeiten/löschen kann.
  • stefan! schrieb:

    Mike schrieb:

    Nunja, was passiert z.B. wenn ich eine Datei verschiebe und gleichzeitig ihren Namen ändere. Ich denke sowas wird knallen und ich muss eben verhindern, dass das überhaupt passiert.

    Wenn du aus mehreren Threads gleichzeitig auf die gleichen Daten zugreifst knallt es gegebenenfalls


    Das ist ja gar nicht möglich, da das System ja schon einen Lock auf eine gerade geöffnete Datei macht. Also während du diese kopierst wirst du sie nicht umbenennen können etc.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Das Ganze ist etwas groß, um das in einem Forumsthema abzufrühstücken, ich gebe trotzdem mal meinen Senf dazu: Du hast grundsätzlich drei Themen, die kaum etwas miteinenander zu tun haben: 1. Nebenläufigkeit, 2. Interface und 3. Unterstützung diverser Betriebssystemversionen.

    Nebenläufigkeit brauchst Du immer dann, wenn Du irgendwelche Vorgänge hast, die länger als ein paar Millisekunden am Stück laufen. Längere Vorgänge dürfen den Hauptthread nicht blockieren, da dieser sich auch um die Benutzerinteraktion kümmern muss. Darüber hinaus ist es immer sinnvoll, wenn Du mehrere voneinander unabhängige Vorgänge hast. Wenn die parallel auf mehreren Prozessorkernen abgearbeitet werden, kann das die Sache ungemein beschleunigen.

    Aber Nebenläufigkeit muss nicht unbedingt Multithreading bedeuten. Cocoa arbeitet oft eventbasiert mit asynchronen Befehlen. I/O ist ein guter Kandidat dafür, denn an Datei- und Netzwerkzugriffen ist der Prozessor kaum beteiligt - es muss nur auf langsame Peripherie gewartet werden. In Deinem Fall wäre FSCopyObjectAsync ideal, ist dummerweise ab 10.8 deprecated. Aber für 10.4 bis 10.7 ist das eine gute Sache (wenn Du 10.4 bis 10.9 unterstützen willst, musst Du wohl entweder Kompromisse eingehen oder verschiedene Pfade für verschiedene Systeme bauen).

    Ich würde mir gut überlegen, ob den Schritt zu Multithreading gehe - das bedeutet immer zusätzliche Komplexität. Das muss sich schon lohnen, meine erste Wahl wäre immer eine eventbasierte, asynchrone API. Aber wenn es schon sein muss, dann nach Möglichkeit eine High-Level-API wie NSOperationQueue. Das geht in den meisten Fällen und stellt Dir einen schmerzarmen, robusten Mechanismus an die Hand. Rohe Threads nur wenn es absolut nicht anders geht - Echte Nebenläufigkeit kann ziemlich ekelig sein. Nicht die Nebenläufigkeit selbst, aber die Notwendigkeit, das Ganze irgendwo wieder zu synchronisieren, und das hast Du immer. Zu grobe Mutexe verringern das Potenzial der Nebenläufigkeit, zu feine machen das ganze kompliziert und erhöhen die Chance, irgendwo Racing Conditions, Life- oder Deadlocks einzubauen. Das zu debuggen ist ekelig.

    tl;dr: Meine Prioritätenliste: 1. Eventbasierte asynchrone Calls, 2. NSOperationQueue, 99. Händische Threads.

    Das GUI-Thema ist ein ganz anderes - Claus hat ja schon erwähnt, dass das Filesystem selbst einen Schutz vor widersprüchlichen gleichzeitigen Dateizugriffen hat. Abgesehen davon kannst Du da keinen hundertprozentigen Schutz selbst machen, denn das Dateisystem kann ja auch von anderen Prozessen parallel geändert werden. Da kann man nur eine für den Zweck sinnvolle Oberfläche bereitstellen sowie Fehler bei den Dateizugriffen prüfen und sinnvoll kommunizieren. Ein app-modaler Fortschrittsbalken ist zwar einfach, aber für den Benutzer wahrscheinlich eher nervig - für den Hausgebrauch vielleicht noch ok, aber wenn Du's verkaufen willst würde ich da etwas Schlaueres machen. Da kommt's darauf an, wieviel Arbeit Du da reinstecken willst.

    Als Letztes: Bist Du sicher, dass Du 10.4 bis 10.9 mit einem Programm abdecken willst? In der Zeit hat sich eine Menge geändert. Wenn es sein muss, bau' Dir Wrapper für Deine Funktionalitäten - dahinter kann man dann die unterschiedlichen Implementierungen verstecken (und hoffentlich irgendwann die Altlasten rausschmeißen).
    Multigrad - 360°-Produktfotografie für den Mac
  • Danke euch für eure Tipps und Anreize. Das mit dem Dateisystem ist schonmal sehr interessant, war mir nicht bewusst, dass es da einen Lock drauf wirft.
    Mit 10.9 mach ich mir auch weniger Gedanken, ich will noch lange nur 10.4-10.6 benutzen. Es handelt sich hierbei ja wirklich nur um ein Programm, dass lediglich für mich ist.
    Das "Problem" mit den mehreren "Pfaden" wegen der unterschiedlichen Systeme hab ich jetzt schon durch den NSFilemanager, der hat sich ja auch geändert zwischen 10.4 und 10.5 (wenn auch die 10.4-Funktionen auch unter 10.6 laufen, ich habs getrennt).
    Eure Ratschläge lassen mich die Welt wieder aus einer anderen Blickrichtung betrachten, danke dafür.
    [self setSignature:null];
    [[self postCount] increment];
  • Jetzt ist mir doch noch eine Frage in den Sinn gekommen:

    Von meine Dateien, die verschoben werden sollen, habe ich in einem NSMutableArray (mySources) die Pfade liegen. Damit nun dem Thread niemand ins Handwerk pfuscht muss/sollte ich natürlich auf mySources einen Lock legen, meine Frage ist nun: Wo mach ich das? In der Methode, die der Thread aufruft oder aber bei setMySources: außerhalb der Threadmethode? Ich bin mir da grad unsicher, wie das die Doku genau meint glaube aber, dass ich das NSLock um setMySources: bauen muss und nicht erst in der Thread-Methode (in der mySources auch verändert wird).
    [self setSignature:null];
    [[self postCount] increment];
  • gritsch schrieb:

    willst du mehrere kopiervorgänge gleichzeitig laufen haben oder nur nacheinander?
    willst du die app app wärend des kopierens weiterhin benutzen (also zb neue zu kopierende dateien hinzufügen) oder nur angezeigt bekommen dass grad was kopiert wird?

    Im Moment verschiebe ich die Dateien nacheinander. Zunächst werden alle Pfade der Dateien, die verschobene werden sollen in ein Array (mySources) gepackt, der Zielordner bekannt gegeben, dann schubst der Thread die Kopiermethode an.

    Das sieht etwa so aus (hab ein wenig gekürzt und denke ich hab das Wesentliche drin):

    Quellcode

    1. [self setMySources:[NSMutableArray arrayWithArray:[myDataSource objectsAtIndexes:indexesFromSelection]]]; //die Dateinamen, NSStrings, aus einer Selektion eines TableViews
    2. [NSThread detachNewThreadSelector:@selector(moveFiles:) toTarget:self withObject:nil];
    3. -(void)moveFiles:(id)sender {
    4. NSAutoreleasepool* myPool = [[NSAutoreleasepool alloc] init];
    5. NSMutableString* destinationPath = [NSMutableString stringWithString:[[self destinationFolder] stringByAppendingPathComponent:[[self mySources] objectAtIndex:0]]];
    6. while ([[ self mySources] count] != 0) {
    7. //hier wird zuerst geschaut ob es den Datei-/Ordnernamen am Zielort schon gibt,
    8. //wenn ja wird dem Zielpfad eine Zahl angehangen
    9. //dann wird die Datei bzw. der Ordner kopiert
    10. [[self mySources] removeObjectAtIndex:0];
    11. }
    12. [NSThread exit];
    13. [myPool release];
    14. }
    Alles anzeigen


    Die Frage ist jetzt eigentlich mehr prinzipbedingt: mySources darf sich ja nicht ändern während der Thread noch in moveItems: unterwegs ist bzw. nur der Thread darf mySources noch ändern. Beispiel: User hat 50 Dateien angeklickt und will sie verschieben. Super, macht der Thread. Jetzt will der User nochmal 2 Dateien verschieben.

    Hier ist es klar, ich kann auch das UI blockieren sodass der User erst gar keinen zweiten Kopiervorgang anschubsen kann. Jetzt ist halt mal ganz allgemein gedacht: Wie müsste ich eine Variable wie mySources lock'en? Ich denke außerhalb der Threadmethode bei setMySources: müsste der Lock rum, oder?

    Wie ja schon eingangs gesagt: Ich beschäftige mich jetzt das erste Mal mit Threads und Co nur weil ich hier jetzt was hab, wo ichs auch wirklich mal ausprobieren kann. Ich für meinen Teil lerne an der Praxis am besten.

    mattik schrieb:

    Mike schrieb:

    In der Methode, die der Thread aufruft oder aber bei setMySources: außerhalb der Threadmethode?

    In beiden. Alle kritischen Abschnitte, die auf eine gemeinsame Ressource zugreifen, müssen entsprechend gelockt werden - so wird sichergestellt, dass nur jeweils ein Thread zur Zeit auf die Ressource zugreift.


    Dann um die komplette Thread-Methode oder nur da, wo meine Variable geändert wird?
    [self setSignature:null];
    [[self postCount] increment];
  • also ich würde vom thread aus nicht auf variablen oder so zugreifen sondern dem thread alle daten (bei mutable collections eventuell eine kopie davon) mitgeben die er braucht.

    beim kopieren von dateien (nichts anderes scheinst du ja zu machen denn das reine verschieben auf der gleichen partition ist extrem schnell) macht es keinen sinn die vorgänge parallel laufen zu lassen weil der kopiervorgang dann eher langsamer ist als wenn du sie in serie ausführst (es kann aber natürlich auch gewollt sein dass du zb gleichzeitig zum kopieren einer großen datei, noch viele kleine dateien kopierst die dann nicht erst nach der großen abgearbeitet werden).