如果我有一个静态类和一个静态方法,如果多个线程同时调用该方法,方法内的局部变量是否安全?
static class MyClass {
static int DoStuff(int n) {
int x = n; // <--- Can this be modified by another thread?
return x++;
}
}
如果我有一个静态类和一个静态方法,如果多个线程同时调用该方法,方法内的局部变量是否安全?
static class MyClass {
static int DoStuff(int n) {
int x = n; // <--- Can this be modified by another thread?
return x++;
}
}
这个问题的答案声称局部变量被存储在堆栈上,因此是线程安全的,这是不完整且可能非常错误的。
当执行静态方法时,线程是否创建自己的作用域?
你的问题包含一个常见的错误。在C#中,“作用域”仅是编译时概念;“作用域”是一个程序文本区域,在这个区域中一个特定的实体(如变量或类型)可以通过其未限定的名称引用。作用域有助于确定编译器如何将名称映射到名称所表示的概念。
线程在运行时不会创建“作用域”,因为“作用域”是纯粹的编译时概念。
局部变量的作用域与其生命周期松散相关。粗略地说,局部变量的运行时生命周期通常从控制线程进入对应作用域的代码开始,并在控制线程离开时结束。但是,如果编译器和运行时认为这样做是有效的或必要的,它们都给予了相当大的自由裁量权来缩短或延长该生存周期。
特别地,迭代器块中的局部变量和匿名函数中闭合的局部变量的生命周期会被延长到控制线程离开作用域之后。
然而,这些都与线程安全无关。因此,让我们放弃这个表述不当的问题并转而讨论一个更好的表述:
如果我有一个静态类和一个静态方法,如果多个线程在调用它,该方法内的实例变量是否安全?
你的问题有一个错误。实例变量是非静态字段。显然,在静态类中没有非静态字段。你将实例变量和局部变量混淆了。你想要问的问题是:
如果我有一个静态类和一个静态方法,如果多个线程在调用它,该方法内的局部变量是否安全?
我不直接回答这个问题,而是将其重新表述为两个更易回答的问题。
在什么情况下需要使用锁定或其他特殊的线程安全技术来确保对变量的安全访问?
如果有两个线程都可以访问变量,其中至少一个线程正在修改它,并且至少一个线程正在执行某些非原子操作,则需要这样做。
(我注意到可能还有其他因素在起作用。例如,如果您需要从每个线程看到的共享内存变量的一致观察值,即使所有操作都是原子的,您也需要使用特殊技术。C# 不能保证标记为volatile的变量的顺序一致性观察。)
太好了。让我们集中精力在“两个线程都可以访问变量”部分。在什么情况下,两个线程可以同时访问局部变量?
在典型情况下,局部变量只能在声明它的方法中访问。每个方法激活将创建一个不同的
static class Foo
{
private static Func<int> f;
public static int Bar()
{
if (Foo.f == null)
{
int x = 0;
Foo.f = ()=>{ return x++; };
}
return Foo.f();
}
}
在这里,“Bar”有一个本地变量“x”。如果在多个线程上调用Bar,则首先竞争的线程会决定谁设置Foo.f。其中一个胜出。从现在开始,对多个线程上的Bar的调用都不安全地操作由获胜线程创建的委托捕获的相同本地变量x。
作为本地变量并不能保证线程安全。第三,迭代器块中的本地变量也有同样的问题:
static class Foo
{
public static IEnumerable<int> f;
private static IEnumerable<int> Sequence()
{
int x = 0;
while(true) yield return x++;
}
public static Bar() { Foo.f = Sequence(); }
}
如果有人调用Foo.Bar(),并从两个不同的线程访问Foo.f,那么单个本地变量x可能会在两个不同的线程上不安全地被改变。(当然,运行迭代器逻辑的机制也不是线程安全的。)