C#中的字符串是不可变的和线程安全的.但是当你有一个公共吸气剂属性时呢?像这样:
public String SampleProperty{ get; private set; }
如果我们有两个线程,第一个是调用'get'而第二个是在"相同"时调用'set',会发生什么?
恕我直言,该集必须锁定为线程安全,如下所示:
private string sampleField; private object threadSafer = new object(); public String SampleProperty{ get{ return this.sampleField; } private set{ lock(threadSafer){ sampleField = value; } } }
Jon Skeet.. 41
大多数答案都使用"原子"这个词,好像原子变化都是需要的.他们通常不是.
这已在评论中提及,但通常不在答案中 - 这是我提供此答案的唯一原因.(关于以较粗粒度锁定,允许附加等内容的观点也是完全有效的.)
通常,您希望读取线程查看变量/属性的最新值.原子性无法保证这一点.作为一个简单的例子,这是一个阻止线程的坏方法:
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
或使用显式内存屏障.这也适用于字符串属性.
大多数答案都使用"原子"这个词,好像原子变化都是需要的.他们通常不是.
这已在评论中提及,但通常不在答案中 - 这是我提供此答案的唯一原因.(关于以较粗粒度锁定,允许附加等内容的观点也是完全有效的.)
通常,您希望读取线程查看变量/属性的最新值.原子性无法保证这一点.作为一个简单的例子,这是一个阻止线程的坏方法:
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
或使用显式内存屏障.这也适用于字符串属性.
引用类型字段的get/set(ldfld/stfld)是(IIRC)保证是原子的,所以这里不应该有任何损坏的风险.因此,从这个角度来看它应该是线程安全的,但我个人将数据锁定在更高的级别 - 即
lock(someExternalLock) { record.Foo = "Bar"; }
或者可能:
lock(record.SyncLock) { record.Foo = "Bar"; }
这允许您对原子操作对同一对象进行多次读取/更新,以便其他线程无法获得无效的对象状态