我目前正在重新设计一个应用程序,该应用程序正在根据输入数据构建模型,并将该数据显示给用户。当前系统具有用于构建模型的线程,用于构建模型的可视化的线程以及显示可视化的线程。我遇到的问题是指针在建模和可视化线程之间传递 - 为了使线程安全,模型中的所有对象都必须有一个互斥锁。这意味着系统中有成千上万的互斥锁活动,当两个线程竞争资源时会有很多停顿。
因此,鉴于这三个线程将存在,以高效且线程安全的方式在建模和可视化线程之间共享数据的最佳方法是什么?有些数据结构很大,会改变建模线程的每个周期,所以我有点不愿意每次传递数据的副本。
编辑:
通过我们希望的系统的总延迟是从接收消息到显示器显示变化的约100ms。如果可能的话,我们希望它更快,但更重要的是我们需要它保持一致 - 由于互斥争用,我们现在看到周期时间的巨大变化。从建模到可视化的数据由2D高度图控制 - 大约18000个单元的数据。模型更新中更新的实际单元格数量要少得多 - 可能只有几百个。
答案 0 :(得分: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平台,请务必使用他们的消息泵。
答案 1 :(得分:2)
如果没有您提供更详细的数据,我已经评论过以任何有意义的方式回答此问题的难度,但无论如何这里有两个提示:
请勿更频繁地更新可视化。根据可视化的复杂性,您应该将显示限制为每秒3或5次更新,因此如果建模线程进展得更快,则不要显示每次迭代。您可以让可视化线程每x毫秒请求一个新数据,或者您可以让建模线程在完成后请求新的可视化,并且自上次可视化以来已经过了足够的时间。
如果可以避免,请不要使用锁定。使用不可变数据的共享指针消除了大量的线程争用,因为您不需要锁定对象(所有访问都是只读的)。通过细粒度的类设计,您还将限制将数据复制到那些真正从一个建模循环更改为下一个建模循环的部分的需求。但是,您可能需要对当前设计进行大量更改。不过,我发现这非常值得。
编辑:在编辑之后,我会建议尽可能地消除锁定,因为您希望尽可能快地显示大量数据,但更改只有5%总数据。
如果您根据修改的数据修改算法从更改单元格到创建新单元格,并且根本不修改模型,只需将智能指针复制到未修改的单元格并创建新模型即可创建新模型对于其余的单元格对象,那么您将不需要对数千个对象中的任何对象进行任何锁定。完成的模型可以传递给可视化线程,并且可以立即创建新模型。类似地,对于可视化线程及其从模型创建的对象 - 可以将其传递给GUI线程,并从当前模型创建新对象。一些细胞将成为几个模型的一部分,一些细胞将只是一个模型的一部分。用于创建可视化和渲染到输出显示的对象也可以共享单元格。智能指针将确保在删除对它们的最后一个引用时正确释放单元格 - 无论发生在哪个线程中。
程序中唯一的锁定是每个线程一个顶级锁定,用于同步对当前模型(或其他类似的顶级对象)的访问。由于执行的操作将非常短,因此延迟不再是问题。除此之外,这种设计将最大限度地利用多个处理器或内核,代价是内存消耗略有增加,CPU周期也会增加。然而,这是使软件在当前和未来硬件上表现更好的最佳方式,这种方式越来越平行。