在测试期间,人们问了我一个问题,但我的答案是错误的(不是我的观点)。你有什么意见?提前谢谢!
我们有以下线程安全实现的String Queue类。它真的是线程安全的,类中没有捕获
public class StringQueue
{
private object _lockObject = new object();
private List<string> _items = new List<string>();
public bool IsEmpty()
{
lock (_lockObject)
return _items.Count == 0;
}
public void Enqueue(string item)
{
lock (_lockObject)
_items.Add(item);
}
public string Dequeue()
{
lock (_lockObject)
{
string result = _items[0];
_items.RemoveAt(0);
return result;
}
}
}
这个类的问题是如果队列为空,它会在Dequeue操作上抛出异常。因此,一位软件开发人员决定向该类添加以下函数,如果Queue为空,则返回null:
public string DequeueOrNull()
{
if (IsEmpty())
return null;
return Dequeue();
}
这个函数DequeueOrNull()线程是否安全?
是的,这个功能是安全的。该函数只调用另外两个安全函数,然后根据定义它仍然是安全的。解释是,对于新版本的C#,当你检查时队列是空的然后你返回null; C#跳过最后一个返回,然后你没有收到任何错误。使用旧版本的C#,最后一次返回是一个问题,然后你可以用这种方式解决它:
public string DequeueOrNull()
{
if (IsEmpty())
return null;
else
return Dequeue();
}
答案 0 :(得分:4)
方法:
public string DequeueOrNull()
{
if (IsEmpty())
return null;
return Dequeue();
}
绝对不是线程安全的(从某种意义上说,你仍然可以获得一个你试图避免的异常)。实际上,这是线程中的常见陷阱。
在这种情况下,它就像是说“好吧,没有其他人触摸过这个。你现在是空的吗?好吧,现在其他所有人都可以触摸它了。” “好吧,没有其他人接触过这个。我知道它不是空的。现在,给我你的东西。好的,其他人都可以触摸它。”
问题在于,在检查它是否为空并从队列中获取项目之间,您已经让其他人访问该对象,因此您检索到的信息可能是“陈旧的”。这是一个相当经典的竞争条件。
旁注:我不会使用使用数组作为队列支持源的集合,但这不是你面试的重点。
奖励积分: 通常,当您对具有多个线程的对象进行操作时,您必须非常小心地管理该对象的状态。
如果有多个线程访问对象可能导致容易出错的状态(比如它使用List<T>
作为后备存储),那么与对象交互的最简单方法是序列化所有请求,但是,您仍然需要注意不要对先前的信息采取行动,如果它是您用来确定它是否会导致错误的信息。如果您正在获取信息以防止出现错误,然后对其进行操作(并假设不会出现错误),那么它们都需要在单个 lock
语句中发生。
额外奖励积分:
在C#中,locks
可以由同一个线程重新输入。所以,你可能能够通过简单地将DequeueOrNull()
方法包装在另一个锁语句中来解决这个问题,如下所示:
public string DequeueOrNull()
{
lock(_lockObject)
{
if (IsEmpty())
return null;
return Dequeue();
}
}
我会把它留给比我更聪明的人,以确定这是否会导致死锁。
答案 1 :(得分:0)
我认为你在这里混淆了两个问题:
您的DequeueOrNull
方法线程安全在某种意义上说您不会弄乱您的数据,因为您没有在该方法中写入列表。
但是,当第一个线程刚刚检查到队列是>时,当另一个线程的出队速度比当前线程更快并且出队时,你不能避免异常 em>不是空的。