为每个对象加载产生一个新线程

时间:2019-10-31 07:59:12

标签: multithreading

我有一个系统,该系统运行多个服务(长期)线程和工作线程(短期)。它们都共享一个包含对象的状态。任何线程都可以随时通过称为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

的I / O开销相比,它可以忽略不计

所以我的问题是:这很可怕吗?这会适得其反吗?

目标平台是常规台式机。但是,该软件可以长时间运行,不会停止:数周甚至数月。

或者...即使我有一个想法,如果每次加载线程都非常糟糕,如何解决这个问题,这可以用另一种方法解决吗?

1 个答案:

答案 0 :(得分:1)

非常有趣!我碰到过几次这个问题,试图将同步接口添加到由服务线程执行的基本异步操作(即文件加载,或者在我的情况下为网络写入)。

我个人的偏好是不提供同步接口。为什么?因为它使代码的设计和实现更加简单,并且易于推理-对于多线程始终很重要。

仅坚持使用单线程和异步的好处是您只有1个服务线程,因此不必担心资源增长,而且始终在同一线程上调用用户回调,从而简化了对用户线程安全的关注ObjectManager(如果您有多个回调线程,则每个用户回调必须是线程安全的,因此这是一个重要的选择)。但是,仅坚持异步接口确实意味着ObjectManager的用户还有更多工作要做。

但是,如果您确实想要保留同步接口,那么我采用的另一种方法可能对您有用。您只使用一个服务线程,但是在loadObjectSync的实现中,您将检查线程ID,以确定调用方是服务线程还是其他线程。如果是其他线程,则将请求排队并安全地阻塞。但是,如果它是服务线程,则可以立即加载该对象,例如,通过调用新函数loadObjectImpl。您将需要在初始化期间获取服务线程的线程ID,并将其存储在ObjectManager实例中,并将其用于线程标识。并且您将需要一个新功能,该功能基本上只是loadingThread函数的内部范围-即,一个名为loadObjectImpl之类的新函数。