0

我目前正在重新设计一个应用程序,该应用程序正在从输入数据构建模型,并将该数据显示给用户。当前系统具有用于构建模型的线程、用于构建模型的可视化的线程和显示可视化的线程。我遇到的问题是指针在建模和可视化线程之间传递——为了使线程安全,模型中的所有对象都必须有一个互斥体。这意味着系统中有数千个互斥锁处于活动状态,由于两个线程都在争夺资源,因此会出现很多停顿。

那么,鉴于这三个线程将存在,以高效和线程安全的方式在建模和可视化线程之间共享数据的最佳方式是什么?一些数据结构很大并且会改变建模线程的每个周期,所以我有点不愿意每次都复制数据。

编辑:

我们希望通过系统的总延迟是从接收消息到显示显示变化的显示大约 100 毫秒。如果可能的话,我们希望它更快,但更重要的是我们需要它保持一致 - 现在我们看到由于互斥体争用导致循环时间的巨大变化。从建模到可视化的数据以 2D 高度图为主——大约 18000 个单元格的数据。不过,模型更新中实际更新的单元数量要少得多——可能只有几百个。

4

2 回答 2

2

我是消息发布/消息泵架构的忠实粉丝。这是 MFC/Win32 提供的用于在线程之间传递数据的主要方法。这种架构是由线程消息驱动的事件,因此当接收线程正在处理线程消息时,它正在处理为线程之间的通信显式更新的数据(参见下面的示例)。

您可以自己实现这一点,并将锁定本地化到每个线程的单独线程消息列表。因此,当一个线程想要向另一个线程发送消息时,您大致执行以下操作

PostThreadMessage(msg, void* param, int paramSize)
{
    lock(msgQueueLock);

    // may wish to copy param
    char paramCpy = malloc
    msgQueue.Queue(msg, pparam, paramSize); 

    unlock(msgQueueLock);
}

那么任何线程的主循环就是

// thread's msg pump
while (1)
{
    // can also use condition var to wait for queue to change...
    lock(msgQueueLock);
    HandleMsgLocally(msgQueue.Deque())
    unlock(msgQueueLock);
}

无论如何,回到 MVC,如果您的模型发生变化,它可以发布到您的视图以更新特定字段,如下所示:

// Well known msg name
int msgName = MODEL_FIELD_A_UPDATED

...

void Model::UpdateFieldA(int newVal)
{
    int* valToCommunicate = new int(newVal)
    PostThreadMessage(MODEL_FIELD_A_UPDATED, valToCommunicate, sizeof(int))
}


...
void HandleMsgLocally(...void * param,)
{
    if (msg == MODEL_FIELD_A_UPDATED)
    {
       int* val = reinterpret_cast<int*>(param);
       //... process param
       delete val;
    }
}

优点是您可以本地化您的锁定。这是巨大的。此外,只要参数被发送者明确理解为新的并被接收者删除,您就不必担心访问共享内存。

这有很多缺点,延迟就是其中之一。如果您需要立即知道发生了什么变化,那么您需要实际制作共享数据并考虑最佳锁定方案。如果您确实需要一个可以随时从多个方向更新的全局状态,那么在我的书中这种情况下单例就可以了。这种设计真的最适合单向的数据,你可以进入竞争条件。但是,您也可以实现锁定 getter 以供一个线程从另一个线程进行检查,但要设置您必须发布的值。有很多变量需要考虑,但希望这可能对您有所帮助。此外,您可能会忘记删除消息中的参数。

更新基于编辑 根据您的信息,根据数据量,发布可以很好地工作。分析您的代码很重要。如果我是你,我会玩一些概念证明并切换到使用条件变量来管理同步性。如果您在 Windows 平台上,请务必使用他们的消息泵。

于 2009-02-17T16:57:36.733 回答
2

我已经评论了在没有您提供更详细数据的情况下以任何有意义的方式回答这个问题的困难,但无论如何这里有两个提示:

  • 不要过于频繁地更新可视化。根据可视化的复杂性,您应该将显示限制为每秒 3 或 5 次更新,因此如果您的建模线程进展得更快,则不要显示每次迭代。您可以让可视化线程每 x 毫秒请求一次新数据,或者您可以让建模线程在完成后请求新的可视化,并且自上次可视化以来已经过去了足够的时间。

  • 如果可以避免,请不要使用锁定。使用指向不可变数据的共享指针消除了很多线程争用,因为您不需要锁定对象(所有访问都是只读的)。通过细粒度的类设计,您还将限制将数据复制到那些真正从一个建模循环到下一个建模循环变化的部分。然而,这可能需要对您当前的设计进行大量更改。不过,我发现这真的很值得。

编辑:编辑后,我更建议尽可能消除锁定,因为您希望尽可能快地显示大量数据,但可能只更改总数据的 5%。

如果您将算法从更改单元格修改为基于修改后的数据创建新单元格,并且您根本不修改模型,而是通过仅将智能指针复制到未修改的单元格并创建新的单元格对象来创建新模型其余的,那么您将不需要对数千个对象中的任何一个进行任何锁定。完成的模型可以传递给可视化线程,并且可以立即创建一个新模型。类似地,对于可视化线程和它从模型创建的对象 - 它可以传递给 GUI 线程,并从当前模型创建一个新对象。一些细胞将是几个模型的一部分,一些细胞将是一个模型的一部分。用于创建可视化和呈现到输出显示的对象也可以共享单元格。

程序中唯一保留的锁定是每个线程一个顶级锁定,以同步对当前模型(或其他类似的顶级对象)的访问。由于执行的操作将非常短,因此延迟应该不再是问题。除此之外,这种设计将最大限度地利用多个处理器或内核,但会消耗更多的内存和更多的 CPU 周期。然而,这是使软件在当前和未来硬件上更好地运行的最佳方法,这些硬件正变得越来越并行。

于 2009-02-17T17:07:45.383 回答