Функція, яка приймає якесь значення і повертає якесь інше значення, і нічого не порушує поза функцією, не має побічних ефектів, і тому є безпечною для потоків. Якщо ви хочете розглянути такі речі, як те, як спосіб виконання функції впливає на споживання енергії, це вже інша проблема.
Я припускаю, що ви маєте на увазі машину, повну Тьюрінга, яка виконує якусь чітко визначену мову програмування, де деталі реалізації не мають значення. Іншими словами, не має значення, що робить стек, якщо функція, яку я пишу мовою програмування на вибір, може гарантувати незмінність у межах цієї мови. Я не думаю про стек, коли програмую на мові високого рівня, і не повинен.
Щоб проілюструвати, як це працює, я запропоную кілька простих прикладів у C #. Для того, щоб ці приклади були правдивими, ми повинні зробити пару припущень. По-перше, компілятор без помилок слідує специфікації C #, а по-друге, він створює правильні програми.
Скажімо, я хочу просту функцію, яка приймає колекцію рядків і повертає рядок, яка є об'єднанням усіх рядків у колекції, розділених комами. Проста, наївна реалізація в C # може виглядати так:
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;
}
Цей приклад незмінний, prima facie. Звідки я це знаю? Тому що string
об’єкт незмінний. Однак реалізація не є ідеальною. Оскільки result
є незмінним, кожен струнний об'єкт повинен створюватися щоразу через цикл, замінюючи початковий об'єкт, на який result
вказує. Це може негативно вплинути на швидкість та чинити тиск на сміттєзбірник, оскільки він повинен очистити всі ці зайві струни.
Тепер, скажімо, я роблю це:
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();
}
Зверніть увагу , що я замінив string
result
із змінним об'єктом, StringBuilder
. Це набагато швидше, ніж перший приклад, оскільки нова струна не створюється кожного разу через цикл. Натомість об'єкт StringBuilder просто додає символи з кожного рядка до колекції символів і в кінці виводить всю справу.
Чи ця функція незмінна, навіть якщо StringBuilder не змінюється?
Так. Чому? Оскільки щоразу, коли ця функція викликається, створюється новий StringBuilder, саме для цього виклику. Тож тепер у нас є чиста функція, яка є безпечною для потоків, але містить змінні компоненти.
Але що робити, якщо я це зробив?
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();
}
}
Чи безпечний цей спосіб для потоків? Ні, це не так. Чому? Тому що зараз клас тримає стан, від якого залежить мій метод. У методі зараз присутня умова перегонів: один потік може змінюватись IsFirst
, але інший потік може виконувати перший Append()
, і в цьому випадку у мене зараз є кома на початку мого рядка, якого, як передбачається, немає.
Чому я можу зробити це так? Ну, я б хотів, щоб нитки накопичували рядки в моєму, result
не зважаючи на порядок, або в тому порядку, в який вони входять. Можливо, це реєстратор, хто знає?
У будь-якому випадку, щоб виправити це, я помістив lock
заяву навколо нутрощів методу.
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();
}
}
}
Тепер знову безпечно для ниток.
Єдиний спосіб, коли мої незмінні методи, можливо, не можуть бути безпечними для потоків - це якщо метод якимось чином просочиться частиною його реалізації. Це могло статися? Не в тому випадку, якщо компілятор правильний і програма правильна. Чи мені колись потрібні будуть замки на таких методах? Ні.
Приклад того, як реалізація могла просочитися в сценарії одночасності, дивіться тут .
but everything has a side effect
- Ага, ні, ні. Функція, яка приймає якесь значення і повертає якесь інше значення, і нічого не порушує поза функцією, не має побічних ефектів, і тому є безпечною для потоків. Не має значення, що комп'ютер використовує електрику. Ми можемо говорити і про космічні промені, що вражають клітини пам'яті, якщо вам це подобається, але давайте збережемо аргумент на практиці. Якщо ви хочете розглянути такі речі, як те, як спосіб виконання функції впливає на споживання енергії, це інша проблема, ніж безпечне програмування потоків.