Funkcja, która przyjmuje pewną wartość i zwraca inną wartość, i nie zakłóca niczego poza funkcją, nie ma skutków ubocznych, a zatem jest bezpieczna dla wątków. Jeśli chcesz zastanowić się, jak sposób działania funkcji wpływa na zużycie energii, jest to inny problem.
Zakładam, że masz na myśli maszynę Turing-complete, która wykonuje jakiś dobrze zdefiniowany język programowania, w którym szczegóły implementacji są nieistotne. Innymi słowy, nie powinno mieć znaczenia, co robi stos, jeśli funkcja, którą piszę w wybranym języku programowania, może zagwarantować niezmienność w obrębie tego języka. Nie myślę o stosie, kiedy programuję w języku wysokiego poziomu, i nie powinienem tego robić.
Aby zilustrować, jak to działa, przedstawię kilka prostych przykładów w języku C #. Aby te przykłady były prawdziwe, musimy poczynić kilka założeń. Po pierwsze, że kompilator przestrzega specyfikacji C # bez błędów, a po drugie, że produkuje poprawne programy.
Powiedzmy, że chcę prostej funkcji, która akceptuje kolekcję ciągów i zwraca ciąg będący konkatenacją wszystkich ciągów w kolekcji oddzielonych przecinkami. Prosta, naiwna implementacja w języku C # może wyglądać następująco:
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;
}
Ten przykład jest niezmienny, prima facie. Skąd to wiem? Ponieważ string
obiekt jest niezmienny. Jednak wdrożenie nie jest idealne. Ponieważ result
jest niezmienny, nowy obiekt ciągu musi być tworzony za każdym razem przez pętlę, zastępując oryginalny obiekt result
wskazujący. Może to negatywnie wpłynąć na prędkość i wywrzeć presję na śmieciarz, ponieważ musi on wyczyścić wszystkie te dodatkowe łańcuchy.
Powiedzmy, że to robię:
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();
}
Zauważ, że mam wymienić string
result
z zmienny obiektu StringBuilder
. Jest to o wiele szybsze niż w pierwszym przykładzie, ponieważ nowy ciąg nie jest tworzony za każdym razem przez pętlę. Zamiast tego obiekt StringBuilder po prostu dodaje znaki z każdego łańcucha do zbioru znaków i generuje całość na końcu.
Czy ta funkcja jest niezmienna, nawet jeśli StringBuilder jest zmienny?
Tak to jest. Czemu? Ponieważ za każdym razem, gdy ta funkcja jest wywoływana, tworzony jest nowy StringBuilder, właśnie dla tego wywołania. Więc teraz mamy czystą funkcję, która jest bezpieczna dla wątków, ale zawiera zmienne komponenty.
Ale co jeśli to zrobię?
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();
}
}
Czy ta metoda jest bezpieczna dla wątków? Nie, nie jest. Czemu? Ponieważ klasa utrzymuje teraz stan, od którego zależy moja metoda. W metodzie występuje teraz warunek wyścigu: jeden wątek może się modyfikować IsFirst
, ale inny wątek może wykonać pierwszy Append()
, w którym to przypadku mam teraz przecinek na początku mojego ciągu, którego nie powinno tam być.
Dlaczego mógłbym chcieć to zrobić w ten sposób? Cóż, może chcę, aby wątki gromadziły ciągi w moim, result
bez względu na kolejność lub w kolejności, w której wątki przychodzą. Może to rejestrator, kto wie?
W każdym razie, aby to naprawić, umieściłem lock
oświadczenie wokół wewnętrznych elementów metody.
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();
}
}
}
Teraz znów jest bezpieczny dla wątków.
Jedynym sposobem, w jaki moje niezmienne metody mogłyby nie być bezpieczne dla wątków, jest to, że metoda w jakiś sposób przecieka część jej implementacji. Czy to może się zdarzyć? Nie, jeśli kompilator jest poprawny, a program jest poprawny. Czy kiedykolwiek będę potrzebować blokad takich metod? Nie.
Aby zapoznać się z przykładem, w jaki sposób można wyciec wdrożenie w scenariuszu współbieżności, zobacz tutaj .
but everything has a side effect
- Nie, nie ma. Funkcja, która przyjmuje pewną wartość i zwraca inną wartość, i nie zakłóca niczego poza funkcją, nie ma skutków ubocznych, a zatem jest bezpieczna dla wątków. Nie ma znaczenia, że komputer zużywa prąd. Możemy mówić o promieniach kosmicznych uderzających również w komórki pamięci, jeśli chcesz, ale utrzymajmy argument w praktyce. Jeśli chcesz zastanowić się, jak sposób działania funkcji wpływa na zużycie energii, jest to inny problem niż programowanie wątkowo bezpieczne.