一个接受某些值并返回其他值的函数,并且不会干扰该函数之外的任何东西,没有副作用,因此是线程安全的。如果您想考虑函数执行方式如何影响功耗等问题,那就是另一个问题。
我假设您所指的是图灵完备的计算机,该计算机正在执行某种定义明确的编程语言,而其中的实现细节无关紧要。换句话说,如果我用自己选择的编程语言编写的函数可以保证在该语言范围内的不变性,那么堆栈在做什么就无关紧要。当我使用高级语言编程时,我不会考虑堆栈,也不必这样做。
为了说明它是如何工作的,我将在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;
}
这个例子是不可变的,表面上是相貌的。我怎么知道 因为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
-呃,不,不是。一个接受某些值并返回其他值的函数,并且不会干扰该函数之外的任何东西,没有副作用,因此是线程安全的。电脑用电都没关系。如果您愿意,我们也可以讨论宇宙射线撞击存储单元的情况,但是让我们保持这种论点的实用性。如果您想考虑函数执行方式如何影响功耗等问题,那就是与线程安全编程不同的问题。