我有一个系统,该系统运行多个服务(长期)线程和工作线程(短期)。它们都共享一个包含对象的状态。任何线程都可以随时通过称为ObjectManager
的单例类来请求对象。如果该对象不可用,则需要加载它。
下面是一些有关对象加载现在情况的伪代码:
class ObjectManager {
getLoadinData(path) {
if (hasLoadingDataFor(path))
return whatWeHave()
else {
loadingData = createNewLoadingData();
loadingData.path = path;
pushLoadingTaskToLoadingThread(loadingData);
return loadingData;
}
}
// loads object and blocks until it's loaded
loadObjectSync(path) {
loadingData = getLoadinData(path);
waitFor(loadingData.conditionVar);
return loadingData.loadedObject;
}
// initiates a load and calls a callback when done
loadObjectAsync(path, callback) {
loadingData = getLoadinData(path);
loadingData.callbacks.add(callback);
}
// dedicated loading thread
loadingThread() {
while (running) {
loadingData = waitForLoadingData();
object = readObjectFromDisk(loadingData.path);
object.onLoaded(); // !!!!
loadingData.object = object;
// unblock cv waiters
loadingData.conditionVar.notifyAll();
// call callbacks
loadingData.callbacks.callAll(object);
}
}
}
问题出在第object.onLoaded
行。我无法控制此功能。一些对象可能会决定它们需要其他对象才有效。因此,在他们的onLoaded
方法中,他们可能会调用loadObjectSync
。哦!这(自然)死锁。它会阻塞加载循环,直到加载循环进行更多迭代为止。
我可以做的就是将onLoaded
调用留给启动线程。这会将loadObjectSync
更改为:
loadObjectSync(path) {
loadingData = getLoadinData(path);
waitFor(loadingData.conditionVar);
if (loadingData.wasCreatedInThisThread()) {
object.onLoaded();
loadingData.onLoadedConditionVar.notifyAll();
loadingData.callbacks.callAll(object);
}
else {
// wait more
waitFor(loadingData.onLoadedConditionVar);
}
return loadingData.loadedObject;
}
...但是问题是如果我没有对loadSync
的调用,而仅对loadAsync
的调用,或者仅仅是loadAsync
的调用是第一个创建加载数据的调用,没有人最终确定对象。因此,为了使这项工作有效,我必须引入另一个线程来完成其loadingData是由loadObjectAsync创建的对象的终结处理。
似乎可以。但是我有一个简单的主意!如果我改成getLoadingData
怎么办。如果这样做,该怎么办:
getLoadinData(path) {
if (hasLoadingDataFor(path))
return whatWeHave()
else {
loadingData = createNewLoadingData();
loadingData.path = path;
///
thread = spawnLoadingThread(loadingData);
thread.detach();
///
return loadingData;
}
}
为每次对象加载生成新线程。因此,没有死锁。每个加载线程都可以安全地阻塞,直到完成。其余代码保持不变。
这意味着潜在地有数十个(或者在某些情况下为什么不成千上万个)活动线程在等待条件变量。我知道产生线程有其开销,但与readObjectFromDisk
所以我的问题是:这很可怕吗?这会适得其反吗?
目标平台是常规台式机。但是,该软件可以长时间运行,不会停止:数周甚至数月。
或者...即使我有一个想法,如果每次加载线程都非常糟糕,如何解决这个问题,这可以用另一种方法解决吗?
答案 0 :(得分:1)
非常有趣!我碰到过几次这个问题,试图将同步接口添加到由服务线程执行的基本异步操作(即文件加载,或者在我的情况下为网络写入)。
我个人的偏好是不提供同步接口。为什么?因为它使代码的设计和实现更加简单,并且易于推理-对于多线程始终很重要。
仅坚持使用单线程和异步的好处是您只有1个服务线程,因此不必担心资源增长,而且始终在同一线程上调用用户回调,从而简化了对用户线程安全的关注ObjectManager
(如果您有多个回调线程,则每个用户回调必须是线程安全的,因此这是一个重要的选择)。但是,仅坚持异步接口确实意味着ObjectManager
的用户还有更多工作要做。
但是,如果您确实想要保留同步接口,那么我采用的另一种方法可能对您有用。您只使用一个服务线程,但是在loadObjectSync
的实现中,您将检查线程ID,以确定调用方是服务线程还是其他线程。如果是其他线程,则将请求排队并安全地阻塞。但是,如果它是服务线程,则可以立即加载该对象,例如,通过调用新函数loadObjectImpl
。您将需要在初始化期间获取服务线程的线程ID,并将其存储在ObjectManager
实例中,并将其用于线程标识。并且您将需要一个新功能,该功能基本上只是loadingThread
函数的内部范围-即,一个名为loadObjectImpl
之类的新函数。