在读取全局字符串之前锁定?

时间:2009-11-20 16:57:09

标签: c# .net multithreading

我有一个课程,而不是让背景工作者做一些处理器密集型的事情。后台工作者读取一些全局声明的字符串...我是否需要锁定这些字符串? backgroundworker从不编写字符串,它们只是表示在类的构造函数中设置的一些目录位置,并且几乎不会在构造函数之后由类写入(并且永远不会被后台工作者写入)。所以可能后台工作者可以读取字符串,因为它也是由主类对象写入的,尽管不太可能。但是,这些操作(后台工作者的读取和主类的写入)是否都不是字符串文字的原子操作?

谢谢, -Robert

编辑:我不关心字符串是否过时或任何事情(这在我的应用中不会是一个大问题),我更担心得到“对象”在别处使用“例外。

7 个答案:

答案 0 :(得分:8)

.NET中的字符串是不可变的;他们无法改变。会发生的是,引用将指向一个完全不同的字符串,但字符串本身不会被更改。

因此,如果您不特别注意后台工作者可能不会使用相同的字符串,如果您更改它,那么您应该没问题。示例:Worker A读取字符串,其他东西更改它,Worker B读取新字符串 - 也许这不会导致问题,也许它会导致问题。但访问字符串本身绝对是安全的。

引用documentation

  

此类型是线程安全的。

ETA:以下评论中Martinho Fernandes的一个非常好的观点:的线程安全对象自动意味着您使用它们执行的所有操作都是线程 - 也是安全的。他甚至写了一篇blog post,这使我不得不再次说出他所做的一切: - )

答案 1 :(得分:3)

如果你不使用锁,最糟糕的情况是你的一个后台工作者从你的主类和线程的角度读取一个过时的字符串副本。 在使用字符串(来自问题)时,您永远不会(在任何情况下)遇到“其他地方使用的对象”异常。

正如另一个答案所说,字符串是不可变的,创建后无法更改。对现有字符串的任何更改都将透明地导致在堆上的内存中创建新字符串,而不会对前一个字符串对象产生任何影响。

使用锁定(以可能会产生可衡量的性能影响为代价),将确保您的后台工作人员读取该字符串的最新副本。

答案 2 :(得分:2)

是的,读取和写入对于字符串变量是原子的。那是因为只有变量引用才会被改变。字符串是不可变的,因此任何修改字符串内容的操作也将创建字符串的新实例。它是通过变量换出的新实例的引用。但是,这不是主要问题。

主要问题与字符串变量本身的陈旧性有关。如果没有适当的同步机制,则可能无法在另一个线程中看到一个线程中的写入。

底线...如果甚至有一个远程机会,另一个线程将修改字符串变量,那么你将需要从工作线程同步访问它,很可能也是你的主线程。

编辑:由于过时并不关心陈旧,所以如果不使用锁定,你可能会没事。但是,假设您已在工作线程启动之前将字符串变量初始化为某些内容。

答案 3 :(得分:1)

如果您没有进行任何写入或修改任何共享变量,那么您不需要使用锁定。

答案 4 :(得分:1)

有几个字符串A B& C?如果后台工作人员正在研究(例如)A和B的v3以及C的v2,这是否重要?如果是这样,那么你需要锁定整个集合的更新。

第二个微妙的问题是C#可能会在寄存器中选择缓存值或者对代码进行重新排序,因此线程看不到“现实”的相同视图。请参阅this discussion以及此SO question的答案。

我的建议,使用同步编写明显正确的代码。在这种情况下,性能影响肯定是微不足道的。这样代码维护者甚至不用担心。如果基准测试表明这是一个性能问题,那么在学习编写线程安全的聪明代码时要非常非常小心。

答案 5 :(得分:0)

是的,我会建议锁定,如果只是阅读这么小的任务,它应该是相当微不足道的。只要知道一个线程锁定一个字符串并等待另一个持有其他锁的线程的死锁。

答案 6 :(得分:0)

您不需要锁定,因为只有在两个线程尝试同时分配值时才会发生此类争用。由于.NET字符串是不可变的,因此您得到的结果永远不会损坏 - 在最坏的情况下,它已经过时了。