Ani*_*Ani 27 .net c# multithreading memory-model thread-safety
我经常听说在.NET 2.0内存模型中,写入始终使用释放围栏.这是真的?这是否意味着即使没有明确的内存屏障或锁定,也不可能在不同于创建它的线程上观察部分构造的对象(仅考虑引用类型)?我显然排除了构造函数泄漏this引用的情况.
例如,假设我们有不可变的引用类型:
public class Person
{
public string Name { get; private set; }
public int Age { get; private set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Run Code Online (Sandbox Code Playgroud)
是否可以使用以下代码观察除"John 20"和"Jack 21"之外的任何输出,例如"null 20"或"Jack 0"?
// We could make this volatile to freshen the read, but I don't want
// to complicate the core of the question.
private Person person;
private void Thread1()
{
while (true)
{
var personCopy = person;
if (personCopy != null)
Console.WriteLine(personCopy.Name + " " + personCopy.Age);
}
}
private void Thread2()
{
var random = new Random();
while (true)
{
person = random.Next(2) == 0
? new Person("John", 20)
: new Person("Jack", 21);
}
}
Run Code Online (Sandbox Code Playgroud)
这是否也意味着我可以创建深度不可变引用类型的所有共享字段,volatile并且(在大多数情况下)只是继续我的工作?
Bri*_*eon 10
我经常听说在.NET 2.0内存模型中,写入始终使用释放围栏.这是真的?
这取决于你所指的模型.
首先,让我们精确定义一个释放栅栏屏障.释放语义规定,在该障碍之后,不允许在指令序列中的障碍之前出现的其他读或写.
因此,在深奥的体系结构(如Windows 8现在将以ARM为目标)上运行的CLI(例如Mono)的另一个实现可能不会在写入时提供释放范围语义.请注意,我说这是可能的,但不确定.但是,在所有正在运行的内存模型之间,例如不同的软件和硬件层,如果您希望代码真正可移植,则必须为最弱的模型编写代码.这意味着针对ECMA模型编码而不做任何假设.
我们应该制作一个显式的内存模型层列表.
这是否意味着即使没有明确的内存屏障或锁定,也不可能在不同于创建它的线程上观察部分构造的对象(仅考虑引用类型)?
是(合格):如果运行应用程序的环境模糊不清,则可能会从另一个线程中观察到部分构造的实例.这是双重检查锁定模式在不使用时不安全的原因之一volatile.但实际上,我怀疑你会遇到这种情况主要是因为微软的CLI实现不会以这种方式重新排序指令.
是否可以使用以下代码观察除"John 20"和"Jack 21"之外的任何输出,例如"null 20"或"Jack 0"?
再次,这是合格的.但由于上述某些原因,我怀疑你会不会观察到这种行为.
虽然,我应该指出,因为person没有标记,因为volatile可能根本没有打印任何东西,因为阅读线程可能总是看到它null.但实际上,我敢打赌,Console.WriteLine调用将导致C#和JIT编译器避免提升操作,否则可能会person在循环外移动读取操作.我怀疑你已经很清楚这种细微差别了.
这是否也意味着我可以让所有深度不可变引用类型的共享字段变为volatile(并且在大多数情况下)继续我的工作?
我不知道.这是一个非常有问题的问题.如果不更好地理解它背后的背景,我会不自在地回答这两种方式.我能说的是,我通常避免使用volatile有利于更多的外显记忆的指令,如Interlocked操作Thread.VolatileRead,Thread.VolatileWrite和Thread.MemoryBarrier.然后,我还尝试完全避免使用无锁代码,以支持更高级别的同步机制,例如lock.
更新:
我喜欢可视化的一种方法是假设C#编译器,JITer等将尽可能积极地进行优化.这意味着Person.ctor可能是内联的候选者(因为它很简单)会产生以下伪代码.
Person ref = allocate space for Person
ref.Name = name;
ref.Age = age;
person = instance;
DoSomething(person);
Run Code Online (Sandbox Code Playgroud)
并且因为写入在ECMA规范中没有发布 - 围栏语义,所以其他读取和写入可以"浮动"到分配之后以person产生以下有效的指令序列.
Person ref = allocate space for Person
person = ref;
person.Name = name;
person.Age = age;
DoSomething(person);
Run Code Online (Sandbox Code Playgroud)
因此,在这种情况下,您可以看到person在初始化之前已分配.这是有效的,因为从执行线程的角度来看,逻辑序列与物理序列保持一致.没有意想不到的副作用.但是,由于显而易见的原因,这个序列对另一个线程来说是灾难性的.
| 归档时间: |
|
| 查看次数: |
1125 次 |
| 最近记录: |