我还有点不清楚,什么时候可以锁定一些代码.我的一般经验法则是在读取或写入静态变量时将操作包装在锁中.但是当只读取静态变量时(例如,它是在类型初始化期间设置的只读),访问它不需要包含在锁定语句中,对吧?我最近看到一些代码看起来像下面的例子,它让我觉得我的多线程知识可能存在一些空白:
class Foo { private static readonly string bar = "O_o"; private bool TrySomething() { string bar; lock(Foo.objectToLockOn) { bar = Foo.bar; } // Do something with bar } }
这对我来说没有意义 - 为什么会出现读取寄存器的并发问题?
此外,这个例子提出了另一个问题.其中一个比另一个好吗?(例如两个持有锁的时间较短?)我想我可以拆卸MSIL ......
class Foo { private static string joke = "yo momma"; private string GetJoke() { lock(Foo.objectToLockOn) { return Foo.joke; } } }
与
class Foo { private static string joke = "yo momma"; private string GetJoke() { string joke; lock(Foo.objectToLockOn) { joke = Foo.joke; } return joke; } }
Matt Howells.. 23
由于您编写的代码都没有在初始化后修改静态字段,因此不需要任何锁定.只是用新值替换字符串也不需要同步,除非新值取决于读取旧值的结果.
静态字段不是唯一需要同步的东西,任何可以修改的共享引用都容易受到同步问题的影响.
class Foo { private int count = 0; public void TrySomething() { count++; } }
您可能会认为执行TrySomething方法的两个线程没问题.但事实并非如此.
线程A将count(0)的值读入寄存器,以便递增.
上下文切换!线程调度程序决定线程A有足够的执行时间.接下来是线程B.
线程B将count(0)的值读入寄存器.
线程B递增寄存器.
线程B保存结果(1)以进行计数.
上下文切换回A.
线程A重新加载寄存器,其值为count(0)保存在其堆栈中.
线程A递增寄存器.
线程A将结果(1)保存到计数.
所以,即使我们两次调用count ++,count的值也只是从0变为1.让代码线程安全:
class Foo { private int count = 0; private readonly object sync = new object(); public void TrySomething() { lock(sync) count++; } }
现在,当线程A被中断时,线程B不会弄乱计数,因为它将触及锁定语句然后阻塞,直到线程A释放同步.
顺便说一下,有一种替代方法可以使Int32s和Int64s增加线程安全性:
class Foo { private int count = 0; public void TrySomething() { System.Threading.Interlocked.Increment(ref count); } }
关于你问题的第二部分,我想我会选择哪个更容易阅读,任何性能差异都可以忽略不计.早期优化是万恶之源等.
为什么线程很难
由于您编写的代码都没有在初始化后修改静态字段,因此不需要任何锁定.只是用新值替换字符串也不需要同步,除非新值取决于读取旧值的结果.
静态字段不是唯一需要同步的东西,任何可以修改的共享引用都容易受到同步问题的影响.
class Foo { private int count = 0; public void TrySomething() { count++; } }
您可能会认为执行TrySomething方法的两个线程没问题.但事实并非如此.
线程A将count(0)的值读入寄存器,以便递增.
上下文切换!线程调度程序决定线程A有足够的执行时间.接下来是线程B.
线程B将count(0)的值读入寄存器.
线程B递增寄存器.
线程B保存结果(1)以进行计数.
上下文切换回A.
线程A重新加载寄存器,其值为count(0)保存在其堆栈中.
线程A递增寄存器.
线程A将结果(1)保存到计数.
所以,即使我们两次调用count ++,count的值也只是从0变为1.让代码线程安全:
class Foo { private int count = 0; private readonly object sync = new object(); public void TrySomething() { lock(sync) count++; } }
现在,当线程A被中断时,线程B不会弄乱计数,因为它将触及锁定语句然后阻塞,直到线程A释放同步.
顺便说一下,有一种替代方法可以使Int32s和Int64s增加线程安全性:
class Foo { private int count = 0; public void TrySomething() { System.Threading.Interlocked.Increment(ref count); } }
关于你问题的第二部分,我想我会选择哪个更容易阅读,任何性能差异都可以忽略不计.早期优化是万恶之源等.
为什么线程很难
读取或写入32位或更小的字段是C#中的原子操作.就我所见,您不需要锁定您提供的代码.