Функция, которая принимает какое-то значение и возвращает какое-то другое значение, и ничего не нарушает вне функции, не имеет побочных эффектов и поэтому является поточно-ориентированной. Если вы хотите рассмотреть такие вещи, как способ выполнения функции влияет на энергопотребление, это другая проблема.
Я предполагаю, что вы имеете в виду машину, полную по Тьюрингу, которая выполняет какой-то четко определенный язык программирования, где детали реализации не имеют значения. Другими словами, не должно иметь значения, что делает стек, если функция, которую я пишу на моем выбранном языке программирования, может гарантировать неизменность в пределах языка. Я не думаю о стеке, когда я программирую на языке высокого уровня, и не должен это делать.
Чтобы проиллюстрировать, как это работает, я собираюсь предложить несколько простых примеров на 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
- Нет, это не так. Функция, которая принимает какое-то значение и возвращает какое-то другое значение, и ничего не нарушает вне функции, не имеет побочных эффектов и поэтому является поточно-ориентированной. Не важно, что компьютер использует электричество. Мы можем говорить о космических лучах, поражающих ячейки памяти, если хотите, но давайте продолжим рассуждать на практике. Если вы хотите рассмотреть такие вещи, как то, как способ выполнения функции влияет на энергопотребление, это другая проблема, чем поточно-ориентированное программирование.