我还是不清楚什么时候应该在一些代码周围包装一个锁。我的一般经验法则是在读取或写入静态变量时将操作包装在锁中。但是,当仅读取静态变量时(例如,它是在类型初始化期间设置的只读),则不需要将其包装在lock语句中,对吗?我最近看到了一些类似于以下示例的代码,这使我认为我的多线程知识可能存在一些不足:
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;
}
}
最佳答案
由于您编写的代码均未在初始化后修改静态字段,因此无需进行任何锁定。仅用新值替换字符串也不需要同步,除非新值取决于读取旧值的结果。
静态字段不是唯一需要同步的事物,任何可以修改的共享引用都容易受到同步问题的影响。
class Foo
{
private int count = 0;
public void TrySomething()
{
count++;
}
}
您可能假设执行TrySomething方法的两个线程就可以了。但事实并非如此。因此,即使我们两次调用count++,count的值也才刚刚从0变为1。让我们使代码成为线程安全的:
class Foo
{
private int count = 0;
private readonly object sync = new object();
public void TrySomething()
{
lock(sync)
count++;
}
}
现在,当线程A被中断时,线程B不会弄乱计数,因为它会打到lock语句,然后阻塞直到线程A释放同步。顺便说一句,还有另一种方法可以使递增的Int32和Int64成为线程安全的:
class Foo
{
private int count = 0;
public void TrySomething()
{
System.Threading.Interlocked.Increment(ref count);
}
}
关于您的问题的第二部分,我想我会选择比较容易理解的那个,任何性能差异都可以忽略不计。早期的优化是万恶之源,等等。Why threading is hard
关于c# - 锁定C#,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/105198/