FP-Befürworter haben behauptet, dass Parallelität einfach ist, weil ihr Paradigma einen veränderlichen Zustand vermeidet. Ich verstehe es nicht.
Ich wollte diese allgemeine Frage als jemand einschätzen, der ein funktionierender Neuling ist, aber über die Jahre hinweg Nebenwirkungen in meinen Augen hatte, und ich möchte sie aus allen möglichen Gründen mildern, auch aus einfacheren (oder speziell "sichereren" Gründen. weniger fehleranfällig "). Wenn ich zu meinen funktionalen Kollegen schaue und sehe, was sie tun, wirkt das Gras ein bisschen grüner und riecht zumindest in dieser Hinsicht besser.
Serielle Algorithmen
Zu Ihrem speziellen Beispiel: Wenn Ihr Problem serieller Natur ist und B erst nach Abschluss von A ausgeführt werden kann, können Sie A und B konzeptionell nicht parallel ausführen, egal was passiert. Sie müssen einen Weg finden, um die Ordnungsabhängigkeit wie in Ihrer Antwort zu unterbrechen, indem Sie parallele Züge mit dem alten Spielstatus ausführen, oder Sie müssen eine Datenstruktur verwenden, mit der Teile davon unabhängig modifiziert werden können, um die in den anderen Antworten vorgeschlagene Ordnungsabhängigkeit zu beseitigen oder so etwas. Aber es gibt definitiv einige konzeptionelle Designprobleme wie diese, bei denen man nicht unbedingt alles so einfach multithreaden kann, weil die Dinge unveränderlich sind. Einige Dinge werden nur serieller Natur sein, bis Sie einen intelligenten Weg finden, um die Auftragsabhängigkeit zu brechen, falls dies überhaupt möglich ist.
Einfachere Parallelität
In vielen Fällen gelingt es uns jedoch nicht, Programme mit Nebenwirkungen an Stellen zu parallelisieren, die möglicherweise die Leistung erheblich verbessern, weil sie möglicherweise nicht threadsicher sind. Einer der Fälle, in denen das Eliminieren des veränderlichen Zustands (oder genauer gesagt der externen Nebenwirkungen) viel hilft, wie ich sehe, ist, dass er "möglicherweise threadsicher" in "definitiv threadsicher" verwandelt oder möglicherweise nicht .
Um diese Aussage ein wenig konkreter zu machen, überlege ich, dass ich Ihnen die Aufgabe gebe, eine Sortierfunktion in C zu implementieren, die einen Komparator akzeptiert und diese verwendet, um ein Array von Elementen zu sortieren. Es ist allgemein gehalten, aber ich gehe davon aus, dass es für Eingaben mit einer solchen Größenordnung (Millionen von Elementen oder mehr) verwendet wird, dass es zweifellos von Vorteil ist, immer eine Multithread-Implementierung zu verwenden. Können Sie Ihre Sortierfunktion mit mehreren Threads ausführen?
Das Problem ist, dass Sie nicht können, weil die Komparatoren Ihre Sortierfunktion möglicherweise aufruftNebenwirkungen verursachen, es sei denn, Sie wissen, wie sie für alle möglichen Fälle implementiert (oder zumindest dokumentiert) sind, was ohne eine Degeneralisierung der Funktion unmöglich ist. Ein Komparator könnte etwas Ekelhaftes tun, wie eine globale Variable in einer nicht-atomaren Weise zu modifizieren. 99,9999% der Komparatoren tun dies möglicherweise nicht, aber wir können diese verallgemeinerte Funktion nicht einfach wegen der 0,00001% der Fälle, die Nebenwirkungen verursachen könnten, multithreaden. Infolgedessen müssen Sie möglicherweise sowohl eine Single-Thread- als auch eine Multithread-Sortierfunktion anbieten und die Verantwortung auf die Programmierer übertragen, die sie verwenden, um auf der Grundlage der Threadsicherheit zu entscheiden, welche verwendet werden soll. Und die Leute könnten immer noch die Single-Thread-Version verwenden und Möglichkeiten zum Multithreading verpassen, weil sie möglicherweise auch unsicher sind, ob der Komparator thread-sicher ist.
Es gibt eine ganze Menge Denkvermögen, das in die Rationalisierung der Thread-Sicherheit von Dingen involviert sein kann, ohne überall Sperren zu werfen, die verschwinden können, wenn wir nur harte Garantien hatten, dass Funktionen für jetzt und für die Zukunft keine Nebenwirkungen verursachen. Und es gibt Angst: praktische Angst, denn jeder, der ein paar Mal zu oft eine Race-Bedingung debuggen musste, würde wahrscheinlich zögern, Multithreading-Vorgänge durchzuführen, bei denen er nicht zu 110% sicher sein kann, dass sie thread-sicher sind und dies auch bleiben werden. Selbst für die paranoidsten (von denen ich wahrscheinlich zumindest an der Grenze bin) bietet die reine Funktion das Gefühl der Erleichterung und des Vertrauens, das wir sicher parallel nennen können.
Und das ist einer der Hauptfälle, in denen ich es als so vorteilhaft betrachte, wenn Sie eine harte Garantie dafür erhalten, dass solche Funktionen thread-sicher sind, die Sie mit reinen funktionalen Sprachen erhalten. Die andere ist, dass funktionale Sprachen oftmals die Schaffung von Funktionen fördern, die in erster Linie frei von Nebenwirkungen sind. Sie können beispielsweise persistente Datenstrukturen bereitstellen, bei denen es recht effizient ist, eine massive Datenstruktur einzugeben und dann eine brandneue auszugeben, bei der nur ein kleiner Teil der Daten vom Original abweicht, ohne das Original zu berühren. Diejenigen, die ohne solche Datenstrukturen arbeiten, möchten diese möglicherweise direkt ändern und verlieren dabei etwas Threadsicherheit.
Nebenwirkungen
Trotzdem bin ich mit einem Teil nicht einverstanden, obwohl ich meinen funktionellen Freunden gebührenden Respekt entgegenbringe (die ich für super cool halte):
[...] weil ihr Paradigma einen veränderlichen Zustand vermeidet.
Es ist nicht unbedingt Unveränderlichkeit, die Parallelität so praktisch macht, wie ich es sehe. Es sind Funktionen, die Nebenwirkungen vermeiden. Wenn eine Funktion ein Array zum Sortieren eingibt, es kopiert und dann die Kopie zum Sortieren des Inhalts mutiert und die Kopie ausgibt, ist sie genauso threadsicher wie eine Funktion, die mit einem unveränderlichen Array-Typ arbeitet, selbst wenn Sie dieselbe Eingabe übergeben Array von mehreren Threads darauf. Ich denke also, dass es immer noch einen Platz für veränderbare Typen gibt, wenn man sozusagen sehr parallelen Code erstellt, obwohl unveränderliche Typen eine Menge zusätzlicher Vorteile bieten, einschließlich beständiger Datenstrukturen, die ich nicht so sehr für ihre unveränderlichen Eigenschaften verwende, sondern für Vermeiden Sie die Kosten für das Kopieren von Inhalten, um Funktionen ohne Nebenwirkungen zu erstellen.
Und es ist oft mit einem Mehraufwand verbunden, Funktionen in Form von Mischen und Kopieren einiger zusätzlicher Daten, möglicherweise einer zusätzlichen Indirektionsebene und möglicherweise einer GC für Teile einer persistenten Datenstruktur, frei von Nebenwirkungen zu machen eine 32-Core-Maschine und ich denke, der Austausch lohnt sich wahrscheinlich, wenn wir sicherer mehr Dinge parallel tun können.