2009-01-12 82 views
27

C#中的字符串是不可变的并且是线程安全的。但是当你有一个公共的getter属性时呢?就像这样:字符串属性本身是否是线程安全的?

public String SampleProperty{ 
    get; 
    private set; 
} 

如果我们有两个线程,第一个是叫“弄”,并在“相同”的时间,二是呼吁“设置”,会发生什么?

恕我直言集必须做出一个锁是线程安全的是这样的:保证是原子,所以有

private string sampleField; 
private object threadSafer = new object(); 

public String SampleProperty{ 
    get{ return this.sampleField; } 
    private set{ 
     lock(threadSafer){ 
      sampleField = value; 
     } 
    } 
} 
+0

“要求”是:所有使用(读取)属性的线程必须具有相同/最新的值。但只有对象本身修改了这个值。 关键字'挥发性'应该保证这一点,或不? – TomTom 2009-01-12 10:37:53

回答

37

大多数答案都使用“原子”一词,就好像原子变化是所有需要的一样。他们通常不是。

这已在评论中提及,但通常不在答案中 - 这是我提供此答案的唯一原因。 (关于以较粗粒度锁定以允许诸如追加之类的问题,也是完全有效的。)

通常您想要一个读线程查看最新的变量/属性的值。那isn't guaranteed by atomicity。作为一个简单的例子,这里有一个办法阻止一个线程:

class BackgroundTaskDemo 
{ 
    private bool stopping = false; 

    static void Main() 
    { 
     BackgroundTaskDemo demo = new BackgroundTaskDemo(); 
     new Thread(demo.DoWork).Start(); 
     Thread.Sleep(5000); 
     demo.stopping = true; 
    } 

    static void DoWork() 
    { 
     while (!stopping) 
     { 
       // Do something here 
     } 
    } 
} 

DoWork完全可能永远循环下去,尽管在写布尔变量是原子 - 没有什么可以从缓存的值停止JIT stopping in DoWork。要解决这个问题,你需要锁定,使变量volatile或使用明确的内存屏障。这也适用于字符串属性。

15

引用类型字段的get/set(ldfld/stfld)是(IIRC)这里不应有任何腐败风险。因此,它应该是线程安全的从角度,而我个人倒在更高层次上对数据进行锁定 - 即

lock(someExternalLock) { 
    record.Foo = "Bar"; 
} 

或可能:

lock(record.SyncLock) { 
    record.Foo = "Bar"; 
} 

这使您可以进行多次读取/更新到同一个对象作为原子操作,以便其他线程无法获得无效的对象状态

+0

TomTom:正如Andreas Huber在我的回答中的评论中指出的那样,您自动锁定的锁定会自动发生,但它不会保护您免受两个线程尝试修改属性的影响,这就是为什么Marc建议锁定属性更高级别),而不是领域(更低级别)。 – 2009-01-12 09:47:13

+0

(题外话,但我会欢迎来自谁捐赠的反馈意见;我个人不会 - 如果回复中有问题,请告诉我) – 2009-01-12 10:04:37

+0

Marc:我的“要求”是所有刚读取值的线程总是具有最新的值。只有“原点”对象应该操纵该值(因此,私人设定者)。所以关键字'volatile'应该符合我的需求。希望。 – TomTom 2009-01-12 10:31:39

4

设置字符串是一个原子操作,即您将获得n新字符串或旧字符串,你永远不会得到垃圾。

如果您正在做一些工作,例如

obj.SampleProperty = "Dear " + firstName + " " + lastName; 

然后字符串concatination全部发生在调用set之前,因此sampleField将始终是新字符串或旧字符串。

但是,如果您的字符串concatination代码是自引用的,例如

obj.SampleProperty += obj.SampleProperty + "a"; 

和其他地方的另一个线程你有

obj.SampleProperty = "Initial String Value"; 

然后,你需要锁定。

考虑你正在使用int。如果你正在分配给int,并且你从int得到的任何值都是有效的,那么你不需要锁定它。

但是,如果INT是保持由两个或多个线程处理,对于数是准确的微件的数量的计数,你需要锁定INT。 字符串的情况也是如此。

我感觉我没有解释这很好,希望它帮助。

感谢

BW

0

这是线程安全的,而无需任何锁定。字符串是引用类型,所以只修改字符串的引用。引用是一种类型保证是原子的(32位系统上的Int32和64位的Int64)。

0

你的第二个代码示例肯定是不对的,因为对变量的访问锁只有当他们在所有地方使用了预期的效果(无论是获得集),所以get还需要锁。

但是,当获取并设置引用类型字段作为像这样的属性时,则添加锁定语句不会添加任何值。指向指针的赋值在.NET环境中保证是原子的,如果多个线程正在改变一个属性,那么无论如何你都有一个固有的竞争条件(线程可能会看到不同的值;这可能是也可能不是问题)指向锁定。

因此,它的作用是,第一段代码很好。但是,您是否真的想将固有的竞争条件构建到多线程应用程序中是另一回事。