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;
}
}
}
答案 0 :(得分:41)
大多数答案都使用“原子”这个词,好像原子变化都是需要的。他们通常不是。
这已在评论中提及,但通常不在答案中 - 这是我提供此答案的唯一原因。 (关于以更粗糙的粒度锁定,允许附加等内容的观点也是完全有效的。)
通常您需要一个阅读线程来查看变量/属性的最新值。那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
中DoWork
的值。要解决此问题,您需要锁定,生成变量volatile
或使用显式内存屏障。这也适用于字符串属性。
答案 1 :(得分:17)
引用类型字段的get / set(ldfld / stfld)是(IIRC)保证是原子的,所以这里不应该有任何损坏的风险。因此,从那个角度来看,它应该是线程安全的,但我个人会将数据锁定在更高的级别 - 即
lock(someExternalLock) {
record.Foo = "Bar";
}
或者也许:
lock(record.SyncLock) {
record.Foo = "Bar";
}
这允许您对同一对象进行多次读取/更新作为原子操作,以便其他线程无法获得无效的对象状态
答案 2 :(得分:4)
设置字符串是一个原子操作,即你将获得新字符串或旧字符串,你永远不会得到垃圾。
如果你正在做一些工作,例如
obj.SampleProperty = "Dear " + firstName + " " + lastName;
然后字符串连接都在调用set之前发生,因此sampleField将始终是新字符串或旧字符串。
但是,如果您的字符串连接代码是自引用的,例如
obj.SampleProperty += obj.SampleProperty + "a";
以及你在另一个主题上的位置
obj.SampleProperty = "Initial String Value";
然后你需要锁。
考虑你正在使用int。如果你要分配int,并且你从int获得的任何值都是有效的,那么你不需要锁定它。
但是,如果int保持由两个或多个线程处理的小部件数量的计数,为了使计数准确,您需要锁定int。 字符串也是如此。
我有一种感觉,我没有很好地解释这一点,希望它有所帮助。
由于
BW
答案 3 :(得分:0)
这是线程安全的,无需锁定。字符串是引用类型,因此仅修改对字符串的引用。参考类型保证是原子的(32位系统上的Int32和64位上的Int64)。
答案 4 :(得分:0)
您的第二个代码示例肯定是不对的,因为在访问变量的所有位置使用锁时,锁只会产生所需的效果(对于get 和设置),所以get
也需要锁定。
但是,当获取并将引用类型字段设置为这样的属性时,添加锁定语句不会添加任何值。指针的赋值在.NET环境中保证是原子的,如果多个线程正在更改属性,那么无论如何你都有一个固有的竞争条件(线程可能看到不同的值;这可能是也可能不是问题)所以几乎没有指向锁定。
所以对于它的作用,第一段代码很好。但是,你是否真的想在多线程应用程序中构建固有的竞争条件是另一回事。