C# 这个线安全吗?
就我所知,以下代码是线程安全的,但需要注意的是,我并不是在寻找单例模式(我不在乎多个线程是否获得不同的MyObject实例,因为MyObject是不可变的。在创建多个实例的并发线程之间发生潜在争用后,后续线程都将获得相同的intance) 以下内容显然不是线程安全的,因为在myObject字段完全初始化之前,多个线程可能会尝试访问该字段:C# 这个线安全吗?,c#,.net,thread-safety,C#,.net,Thread Safety,就我所知,以下代码是线程安全的,但需要注意的是,我并不是在寻找单例模式(我不在乎多个线程是否获得不同的MyObject实例,因为MyObject是不可变的。在创建多个实例的并发线程之间发生潜在争用后,后续线程都将获得相同的intance) 以下内容显然不是线程安全的,因为在myObject字段完全初始化之前,多个线程可能会尝试访问该字段: private static MyObject myObject; ... public static MyObject GetMyObject() {
private static MyObject myObject;
...
public static MyObject GetMyObject()
{
if (myObject == null)
{
myObject = new MyObject();
myObject.Initialize(...);
}
return myObject;
}
以下是线程安全的,还是myObject字段上需要volatile关键字?它看起来是安全的,因为在对象完全初始化之前,myObject字段不会被分配。但我担心抖动可能会内联LoadMyObject方法,并基本上将其重新实现为上面的代码,而这不是线程安全
private static MyObject myObject;
...
private static MyObject LoadMyObject()
{
MyObject myObject = new MyObject();
myObject.Initialize(...);
return myObject;
}
public static MyObject GetMyObject()
{
if (myObject == null)
{
myObject = LoadMyObject();
}
return myObject;
}
上面的背景是,我想重构一些多线程代码,并用对工厂方法的调用替换对构造函数的调用。我想知道这样做是否会破坏线程安全
编辑
重申一下,我不在乎是否创建了MyObject的多个实例,只在乎无法访问未完全初始化的实例。作为一个具体的例子,请考虑一个加载了配置文件内容的只读XmlDocument。我希望在内存中提供对XmlDocument的静态引用,以便避免每次访问时从磁盘加载的开销。但我不在乎两个线程是否同时运行,并且都从磁盘加载文档
EDIT2
如果我理解正确,C语言规范中的以下语句似乎暗示抖动不会对代码重新排序,因此我的最后一个示例应该是线程安全的。我理解正确吗
3.10执行令
...
数据依赖性保存在
执行线程。即
每个变量的值计算为
如果线程中的所有语句都是
按原始程序顺序执行
如果我能为clippy频道一分钟:“看起来你在尝试实现单例模式”。请参阅。如果我能为clippy频道一分钟:“看起来你在尝试实现单例模式”。请参阅。为什么不将Initialize方法移动到MyObject的c'tor中,并用一个调用对其进行初始化?为什么不将Initialize方法移动到MyObject的c'tor中,并用一个调用对其进行初始化?即使没有编译器优化,它仍然不是线程安全的。Volatile在这种情况下没有帮助,它只是为了保护假设两个线程在完全相同的时间通过空检查-您最终会调用“LoadMyObject”两次,在这种情况下这可能是正常的,也可能不是。这是一个TOCTOU错误(检查时间/使用时间)。您基本上必须使整个GetMyObject方法的主体安全,包括检查(即通过使该调用同步)。即使没有编译器优化,它仍然不是线程安全的。Volatile在这种情况下没有帮助-它只是为了保护变量不被“隐藏”修改而设计的。假设两个线程在同一时间通过空检查-您将调用“LoadMyObject”两次,在本例中这可能是正常的,也可能不是。这是一个TOCTOU错误(检查时间/使用时间)。您必须确保整个GetMyObject方法的主体安全,包括检查(即使该呼叫同步)。以下是我的看法:
public class TestClass
{
private static TestClass instance = LoadObject();
private TestClass(){ }
private static TestClass LoadObject()
{
var t = new TestClass();
//do init
return t;
}
public static TestClass GetObject()
{
return instance;
}
}
以下是我的看法:
public class TestClass
{
private static TestClass instance = LoadObject();
private TestClass(){ }
private static TestClass LoadObject()
{
var t = new TestClass();
//do init
return t;
}
public static TestClass GetObject()
{
return instance;
}
}
从某种意义上讲,您将获得一个正确初始化的对象是安全的,但是多个线程可能同时创建对象,并且一个线程可能返回一个不同线程刚刚创建的对象 如果编译器内联该方法,它仍然不会与第一个代码相同。由于对对象的引用在初始化之前一直保存在局部变量中,因此仍然是安全的 您可以自己内联该方法,无需将代码放在单独的方法中以确保安全:
public static MyObject GetMyObject() {
if (myObject == null) {
MyObject newObject = new MyObject();
newObject.Initialize(...);
myObject = newObject;
}
return myObject;
}
如果要防止多个对象由单独的线程创建,则需要使用锁。从安全的意义上讲,可以获得正确初始化的对象,但多个线程可能同时创建对象,并且一个线程可能返回另一个线程刚刚创建的对象 如果编译器内联该方法,它仍然不会与第一个代码相同。由于对对象的引用在初始化之前一直保存在局部变量中,因此仍然是安全的 您可以自己内联该方法,无需将代码放在单独的方法中以确保安全:
public static MyObject GetMyObject() {
if (myObject == null) {
MyObject newObject = new MyObject();
newObject.Initialize(...);
myObject = newObject;
}
return myObject;
}
如果要防止多个对象由单独的线程创建,则需要使用锁。要回答您的第二次编辑,如果初始化方法需要一段时间,则如果对象不为null,则另一个线程可能会将其视为k,然后将其非初始化状态拉入。我建议您使用锁进行null检查d初始化,以确保仅在完全初始化时检索对象 如果您希望它是完全线程安全的,这将有助于:
private static MyObject myObject;
private object lockObj = new object();
...
public static MyObject GetMyObject()
{
lock(lockObj)
{
if (myObject == null)
{
myObject = new MyObject(...);
}
}
return myObject;
}
为了回答您的第二次编辑,如果初始化方法需要一点时间,那么如果对象不为null,则另一个线程可能会将其视为k,然后将其拉入未初始化状态。我建议您对null检查和初始化使用锁,以确保仅在完全初始化时检索对象 如果您希望它是完全线程安全的,这将有助于:
private static MyObject myObject;
private object lockObj = new object();
...
public static MyObject GetMyObject()
{
lock(lockObj)
{
if (myObject == null)
{
myObject = new MyObject(...);
}
}
return myObject;
}
在我看来,这似乎过于优化了。与其深入了解编译器正在做的事情的微妙之处,不如选择其中最适合您的一个:
- 一个实例(单例)
- 每个线程一个实例(线程静态)