具有属性赋值的线程安全构造

时间:2018-04-05 00:53:10

标签: c# multithreading constructor

来自https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers

class Cat
{
    // Auto-implemented properties.
    public int Age { get; set; }
    public string Name { get; set; }
}

Cat cat = new Cat { Age = 10, Name = "Fluffy" };
     

对象初始化器语法允许您创建实例,和   之后,它将新创建的对象及其指定的属性分配给赋值中的变量。

vs https://stackoverflow.com/a/19138412/432976

var albumData = new Album 
{
     Name = "Albumius",
     Artist = "Artistus",
     Year = 2013
 };
     

是此等效代码的语法简写:

var albumData = new Album();
albumData.Name = "Albumius";
albumData.Artist = "Artistus";
albumData.Year = 2013;
     

编译后两者完全相同。

问题: 这个结构+赋值线程安全吗? (也就是说,另一个阅读猫的线程是否可以在创建它之间以及分配年龄和名称时看到Cat?)

第一个似乎是说它是,因为它是在分配属性之后,变量被赋值(线程安全顺序),第二个说在编译的代码级别,顺序是不同的。

如果第二个为真,下面的代码是否足以避免另一个线程看到半个构造的猫的竞争条件?

var myNewCat = new Cat { Age = 10, Name = "Fluffy" };
sharedCat = myNewCat;

我意识到这里存在次要竞争条件,如果其他线程看到oldCat或newCat,但在这种情况下我唯一关心的是其他线程必须看到一个完整的Cat,而不是一半构造的。

3 个答案:

答案 0 :(得分:2)

@JonSkeet says等效代码引入了一个临时变量。

var tmp = new Album();
tmp.Name = "Albumius";
tmp.Artist = "Artistus";
tmp.Year = 2013;
var albumData = tmp;

或者猫

var tmp = new Cat();
tmp.Age = 10;
tmp.Name = "Fluffy";
Cat cat = tmp;

因此,如果引用赋值是线程安全的,那么对象初始化器将是线程安全的。正确?

答案 1 :(得分:1)

使用以下示例类

public class Cat
{
    public int Age { get; set; }
    public string Name { get; set; }
}

C#2.0样式

代码

var cat1 = new Cat();
cat1.Age = 10;
cat1.Name = "Fluffy";

生成以下IL代码(使用.Net Reflector进行检查)

L_000c: newobj instance void ConsoleApp1.Cat::.ctor()
L_0011: stloc.0 
L_0012: ldloc.0 
L_0013: ldc.i4.s 10
L_0015: callvirt instance void ConsoleApp1.Cat::set_Age(int32)
L_001a: nop 
L_001b: ldloc.0 
L_001c: ldstr "Fluffy"
L_0021: callvirt instance void ConsoleApp1.Cat::set_Name(string)
L_0026: nop 

这基本上创建了变量实例及其可用(来自stloc.0),所以此时它可供另一个线程在该状态下接收它(如果它被暴露)。

  

答案是否此版本不是线程安全的

C#3.0样式

从C#3开始,我们可以做所谓的Object Initializers

代码

var cat1 = new Cat { Age = 10, Name = "Fluffy" };

生成以下IL代码

L_000c: newobj instance void ConsoleApp1.Cat::.ctor()
L_0011: dup 
L_0012: ldc.i4.s 10
L_0014: callvirt instance void ConsoleApp1.Cat::set_Age(int32)
L_0019: nop 
L_001a: dup 
L_001b: ldstr "Fluffy"
L_0020: callvirt instance void ConsoleApp1.Cat::set_Name(string)
L_0025: nop 
L_0026: stloc.0 

这里的主要区别是类的实例永远不会从评估堆栈中拉出来,因为它使用dup直到它完成所有操作然后 stloc.0 为止。

  

答案是这种方法是线程安全的

答案 2 :(得分:-1)

如果一段代码仅以保证多个线程同时安全执行的方式操作共享数据结构,则它是线程安全的。 在您按原样提供的示例中,没有共享状态,因为实例已分配给局部变量。因此,无论哪种方式(是的,它们都是相同的)总是线程安全的:

StaticAssetStorageService

一旦分配的变量不是它所在的方法(例如类的字段)的本地变量,情况就会发生变化:

var albumData = new Album 
{
     Name = "Albumius",
     Artist = "Artistus",
     Year = 2013
 };

如您所见,现在可以同时从不同的线程调用class TestClass { private Album album; public void TestAssignment(string name) { // Here I'm using this style of property assignment to make it very explicit why it's not thread-safe this.album = new Album(); this.album.Name = name; ... } } 方法。根据执行每一行的线程,将为相册实例及其名称分配不同的值。 在这种情况下,考虑线程安全机制是有意义的。

希望这有助于澄清差异。