Eine Funktion, die einen bestimmten Wert akzeptiert und einen anderen Wert zurückgibt und nichts außerhalb der Funktion stört, hat keine Nebenwirkungen und ist daher threadsicher. Wenn Sie überlegen möchten, wie sich die Funktionsausführung auf den Stromverbrauch auswirkt, ist dies ein anderes Problem.
Ich gehe davon aus, dass Sie sich auf eine Turing-complete-Maschine beziehen, die eine bestimmte Programmiersprache ausführt, bei der die Implementierungsdetails irrelevant sind. Mit anderen Worten, es sollte keine Rolle spielen, was der Stack tut, wenn die Funktion, die ich in der Programmiersprache meiner Wahl schreibe, Unveränderlichkeit innerhalb der Grenzen der Sprache garantieren kann. Ich denke nicht an den Stack, wenn ich in einer höheren Sprache programmiere, und ich sollte auch nicht müssen.
Um zu veranschaulichen, wie dies funktioniert, werde ich einige einfache Beispiele in C # anbieten. Damit diese Beispiele zutreffen, müssen wir einige Annahmen treffen. Erstens, dass der Compiler die C # -Spezifikation fehlerfrei befolgt und zweitens, dass er korrekte Programme erstellt.
Angenommen, ich möchte eine einfache Funktion, die eine Zeichenfolgensammlung akzeptiert und eine Zeichenfolge zurückgibt, die eine Verkettung aller Zeichenfolgen in der Sammlung ist, die durch Kommas getrennt sind. Eine einfache, naive Implementierung in C # könnte folgendermaßen aussehen:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
string result = string.Empty;
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result += s;
else
result += ", " + s;
}
return result;
}
Dieses Beispiel ist auf den ersten Blick unveränderlich. Woher weiß ich das? Weil das string
Objekt unveränderlich ist. Die Implementierung ist jedoch nicht ideal. Da result
unveränderlich ist, muss jedes Mal durch die Schleife ein neues Zeichenfolgenobjekt erstellt werden, das das ursprüngliche Objekt ersetzt, auf das result
verwiesen wird. Dies kann sich negativ auf die Geschwindigkeit auswirken und Druck auf den Abfallsammler ausüben, da alle diese zusätzlichen Zeichenfolgen entfernt werden müssen.
Angenommen, ich mache Folgendes:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
var result = new StringBuilder();
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
Beachten Sie, dass ich durch string
result
ein veränderliches Objekt ersetzt habe StringBuilder
. Dies ist viel schneller als im ersten Beispiel, da nicht jedes Mal eine neue Zeichenfolge durch die Schleife erstellt wird. Stattdessen fügt das StringBuilder-Objekt die Zeichen aus jeder Zeichenfolge zu einer Sammlung von Zeichen hinzu und gibt das Ganze am Ende aus.
Ist diese Funktion unveränderlich, obwohl StringBuilder veränderlich ist?
Ja ist es. Warum? Da bei jedem Aufruf dieser Funktion ein neuer StringBuilder nur für diesen Aufruf erstellt wird. Jetzt haben wir also eine reine Funktion, die threadsicher ist, aber veränderbare Komponenten enthält.
Aber was ist, wenn ich das tue?
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
public string ConcatenateWithCommas(ImmutableList<string> list)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
Ist diese Methode threadsicher? Nein, das ist es nicht. Warum? Weil die Klasse jetzt den Status hält, von dem meine Methode abhängt. In der Methode ist jetzt eine Racebedingung vorhanden: Ein Thread kann ändern IsFirst
, aber ein anderer Thread kann den ersten ausführen Append()
. In diesem Fall habe ich jetzt ein Komma am Anfang meiner Zeichenfolge, das nicht vorhanden sein soll.
Warum könnte ich es so machen wollen? Nun, ich möchte vielleicht, dass die Threads die Strings result
unabhängig von der Reihenfolge oder in der Reihenfolge, in der die Threads eingehen, in meinen akkumulieren . Vielleicht ist es ein Logger, wer weiß?
Wie auch immer, lock
um das Problem zu beheben, habe ich eine Aussage über die Innereien der Methode gemacht.
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
private static object locker = new object();
public string AppendWithCommas(ImmutableList<string> list)
{
lock (locker)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
}
Jetzt ist es wieder threadsicher.
Die einzige Möglichkeit, dass meine unveränderlichen Methoden möglicherweise nicht threadsicher sind, besteht darin, dass die Methode einen Teil ihrer Implementierung verliert. Könnte das passieren? Nicht, wenn der Compiler korrekt ist und das Programm korrekt ist. Brauche ich jemals Sperren für solche Methoden? Nein.
Ein Beispiel dafür, wie die Implementierung in einem Parallelitätsszenario möglicherweise durchgesickert sein kann, finden Sie hier .
but everything has a side effect
- Nein, tut es nicht. Eine Funktion, die einen bestimmten Wert akzeptiert und einen anderen Wert zurückgibt und nichts außerhalb der Funktion stört, hat keine Nebenwirkungen und ist daher threadsicher. Es spielt keine Rolle, dass der Computer Strom verbraucht. Wenn Sie möchten, können wir auch über kosmische Strahlung sprechen, die auf Gedächtniszellen trifft, aber lassen Sie uns das Argument praktisch belassen. Wenn Sie berücksichtigen möchten, wie sich die Funktionsausführung auf den Stromverbrauch auswirkt, ist dies ein anderes Problem als die threadsichere Programmierung.