在c#中,每个线程都有自己的堆栈空间。
如果是这种情况,为什么以下代码不是线程安全的? (声明此帖子 在此帖子中是线程安全的:Locking in C#
class Foo
{
private int count = 0;
public void TrySomething()
{
count++;
}
}
由于count是一个int(堆栈变量),当然这个值会被隔离到一个单独的线程,在它自己的堆栈上,因此是线程安全的吗? < / p>
我可能在这里遗漏了一些东西,但我不明白线程本地存储中的实际内容,如果不是线程的基于堆栈的变量?
此外,本地声明的变量如何:
class Foo
{
public void TrySomething(object myObj)
{
var localVariable = new object();
localVariable = myObj;
}
}
这里对局部变量有什么影响?它仍然是基于堆的吗?它是线程安全的吗?
答案 0 :(得分:3)
Count是类Foo
的成员变量。由于Foo
是引用类型,因此它存储在堆而不是堆栈中。
如果你只创建了1个Foo
对象并允许两个单独的线程来调用TrySomething()
方法,那么它们都会在同一个对象上调用该方法(存储在堆上),两者都会尝试增加同一个成员,因为它是同一个对象的一部分。 (见Darin的sample code)
结构和类之间的区别不在于它们是存储在堆还是堆栈上。这是一个普遍的误解。将类视为堆上存储的指针。将结构视为本地存储的直接值。在一个非常简单的层面上,这意味着结构通常存储在堆栈中,在所有情况下肯定都不是这样,如本例所示。
答案 1 :(得分:2)
但是count 不是一个堆栈变量;它是类的成员变量。如果将对Foo
实例的引用传递给另一个线程,则表示您处于非线程安全状态。
线程本地存储(TLS)与成员变量完全不同。 TLS将值与线程关联,而不与特定类关联。将其视为全局变量(或类的静态成员变量),除了它们的范围仅在单个线程中可见;其他线程甚至看不到。它们不存储在线程的堆栈中,而是存储在线程专用的特殊区域中。
但是,仍然有麻烦的余地。如果在线程中存储对TLS中的Foo
的引用,则它最初对其他线程不可见。如果你以某种方式将该引用复制到另一个线程中,那么无论第一个引用是否存在于TLS中,你都会处于线程不安全的状态。
答案 2 :(得分:2)
此代码不是线程安全的,因为两个线程可以在Foo
的同一个实例上执行此方法,这意味着count
变量将是相同的:
var foo = new Foo();
new Thread(foo.TrySomething).Start();
new Thread(foo.TrySomething).Start();
TrySomething方法将使用相同的count
变量在两个不同的线程上并发执行,因此必须同步对此count
变量的每次访问:
public void TrySomething()
{
Interlocked.Increment(ref count);
}