检查 null 线程安全吗?
Are checks for null thread-safe?
我有一些代码在新线程上抛出异常,我需要在主线程上确认和处理这些异常。为此,我通过使用包含抛出异常的字段在线程之间共享状态。
我的问题是我在检查 null 时是否需要使用锁,就像我在以下代码示例中所做的那样?
public class MyClass
{
readonly object _exceptionLock = new object();
Exception _exception;
public MyClass()
{
Task.Run(() =>
{
while (CheckIsExceptionNull())
{
// This conditional will return true if 'something has gone wrong'.
if(CheckIfMyCodeHasGoneWrong())
{
lock(_exceptionLock)
{
_exception = new GoneWrongException();
}
}
}
});
}
bool CheckIsExceptionNull() // Is this method actually necessary?
{
lock (_exceptionLock)
{
return _exception == null;
}
}
// This method gets fired periodically on the Main Thread.
void RethrowExceptionsOnMainThread()
{
if (!CheckIsExceptionNull())
{
lock (_exceptionLock)
{
throw _exception; // Does this throw need to be in a lock?
}
}
}
}
此外,在主线程抛出异常时是否需要使用锁?
首先要注意的是你的代码不是线程安全的,因为你有一个线程竞争:你检查 CheckIsExceptionNull
在一个不同的锁定区域到你抛出的地方,但是: 值可以在测试和抛出之间改变.
不保证字段访问是线程安全的。特别是,虽然引用不能被撕裂(这是有保证的——引用的读写是原子的),但不能保证不同的线程由于 CPU 缓存等原因,将看到最新的值。实际上不太可能咬你,但这是一般情况下线程问题的问题;p
就我个人而言,我可能只是让该字段可变,并使用本地。例如:
var tmp = _exception;
if(tmp != null) throw tmp;
以上没有线程竞争。添加:
volatile Exception _exception;
确保该值未缓存在寄存器中(尽管从技术上讲这是副作用,而不是 volatile
的预期/记录效果)
关于此类问题,可能有很多有趣且有用的信息。您可以花几个小时阅读有关它的博客 post 和书籍。即使是真正了解它的人也不同意。 (正如您在 Marc Gravell 和 Eric Lippert 的 post 中看到的那样)。
所以我没有回答这个问题。只是一个建议:始终使用锁。 只要超过一个线程正在访问一个字段。没有风险,没有猜测,没有讨论编译器和 CPU 技术。只需使用锁并保存并使其不仅在大多数情况下在您的机器上工作,而且在所有情况下在每台机器上工作。
进一步阅读:
我有一些代码在新线程上抛出异常,我需要在主线程上确认和处理这些异常。为此,我通过使用包含抛出异常的字段在线程之间共享状态。
我的问题是我在检查 null 时是否需要使用锁,就像我在以下代码示例中所做的那样?
public class MyClass
{
readonly object _exceptionLock = new object();
Exception _exception;
public MyClass()
{
Task.Run(() =>
{
while (CheckIsExceptionNull())
{
// This conditional will return true if 'something has gone wrong'.
if(CheckIfMyCodeHasGoneWrong())
{
lock(_exceptionLock)
{
_exception = new GoneWrongException();
}
}
}
});
}
bool CheckIsExceptionNull() // Is this method actually necessary?
{
lock (_exceptionLock)
{
return _exception == null;
}
}
// This method gets fired periodically on the Main Thread.
void RethrowExceptionsOnMainThread()
{
if (!CheckIsExceptionNull())
{
lock (_exceptionLock)
{
throw _exception; // Does this throw need to be in a lock?
}
}
}
}
此外,在主线程抛出异常时是否需要使用锁?
首先要注意的是你的代码不是线程安全的,因为你有一个线程竞争:你检查 CheckIsExceptionNull
在一个不同的锁定区域到你抛出的地方,但是: 值可以在测试和抛出之间改变.
不保证字段访问是线程安全的。特别是,虽然引用不能被撕裂(这是有保证的——引用的读写是原子的),但不能保证不同的线程由于 CPU 缓存等原因,将看到最新的值。实际上不太可能咬你,但这是一般情况下线程问题的问题;p
就我个人而言,我可能只是让该字段可变,并使用本地。例如:
var tmp = _exception;
if(tmp != null) throw tmp;
以上没有线程竞争。添加:
volatile Exception _exception;
确保该值未缓存在寄存器中(尽管从技术上讲这是副作用,而不是 volatile
的预期/记录效果)
关于此类问题,可能有很多有趣且有用的信息。您可以花几个小时阅读有关它的博客 post 和书籍。即使是真正了解它的人也不同意。 (正如您在 Marc Gravell 和 Eric Lippert 的 post 中看到的那样)。
所以我没有回答这个问题。只是一个建议:始终使用锁。 只要超过一个线程正在访问一个字段。没有风险,没有猜测,没有讨论编译器和 CPU 技术。只需使用锁并保存并使其不仅在大多数情况下在您的机器上工作,而且在所有情况下在每台机器上工作。
进一步阅读: