当双缓冲数据应在线程之间共享时,我使用了一个系统,其中一个线程从一个缓冲区读取,一个线程从另一个缓冲区读取并从第一个缓冲区读取。问题是,我将如何实现指针交换?我需要使用临界区吗?没有可用的互锁函数可以实际交换值。我不能让线程一从缓冲区一读取,然后从缓冲区二开始读取,在读取过程中,这将是应用程序崩溃,即使另一个线程没有开始写入它。
我在 Visual Studio Ultimate 2010 RC 中的 Windows 上使用本机 C++。
当双缓冲数据应在线程之间共享时,我使用了一个系统,其中一个线程从一个缓冲区读取,一个线程从另一个缓冲区读取并从第一个缓冲区读取。问题是,我将如何实现指针交换?我需要使用临界区吗?没有可用的互锁函数可以实际交换值。我不能让线程一从缓冲区一读取,然后从缓冲区二开始读取,在读取过程中,这将是应用程序崩溃,即使另一个线程没有开始写入它。
我在 Visual Studio Ultimate 2010 RC 中的 Windows 上使用本机 C++。
使用临界区是公认的方法。CRITICAL_SECTION
只需在所有线程之间共享一个对象,EnterCriticalSection
然后LeaveCriticalSection
围绕指针操作/缓冲区读取/写入代码调用该对象。尝试尽快完成您的关键部分,尽可能多地在关键部分之外留下代码。
即使您使用双互锁交换技巧,您仍然需要一个关键部分或其他东西来同步您的线程,所以也不妨将它用于此目的。
对我来说,这听起来像是一个读写器互斥类型的问题。
假设您有两个缓冲区 B1 和 B2,并且您有两个线程 T1 和 T2。如果 T1 使用 B1 而 T2 使用 B2 则可以。“使用”是指读取和/或写入缓冲区。然后在某个时候,缓冲区需要交换,以便 T1 使用 B2 而 T2 使用 B1。您必须注意的是,交换是在两个线程都没有访问其缓冲区时完成的。
假设您只使用了一个简单的互斥锁。T1 可以获取互斥体并使用 B1。如果 T2 想使用 B2,则必须等待互斥体。当 T1 完成时,T2 将解除阻塞并与 B2 一起工作。如果任何一个线程(或某些第三方线程)想要交换缓冲区,它也必须使用互斥锁。因此,仅使用一个互斥锁会序列化对缓冲区的访问——不太好。
如果您改用读写器互斥锁,它可能会更好。T1 可以获取互斥锁上的读锁并使用 B1。T2 还可以获取互斥锁上的读锁并使用 B2。当其中一个线程(或第三方线程)决定是时候交换缓冲区时,它必须对互斥锁进行写锁定。在没有更多的读锁之前,它将无法获得写锁。此时,它可以交换缓冲区指针,知道没有人在使用任何一个缓冲区,因为当互斥体上有写锁时,所有读锁尝试都会阻塞。
为什么不能使用InterlockedExchangePointer ?
编辑:好的,我明白你现在在说什么,IEP 实际上并没有相互交换 2 个实时指针,因为它只需要一个引用值。
您必须构建自己的函数来交换使用信号量或关键部分来控制它的指针。需要为指针的所有用户添加相同的保护,因为任何读取正在修改的指针的代码都是错误的。
管理此问题的一种方法是让所有指针操作逻辑在锁的保护下工作。
看,我最初确实设计了线程,以便它们完全异步并且在它们的常规操作中不需要任何同步。但是,由于我在线程池中基于每个对象执行操作,如果给定对象由于当前正在同步而无法读取,我可以在等待时再做一个。从某种意义上说,我可以同时等待和操作,因为我有很多线程要处理。
创建两个关键部分,每个线程一个。渲染时,按住 render crit 部分。不过,另一个线程仍然可以对另一个暴击部分做它喜欢的事情。使用 TryEnterCriticalSection,如果它被持有,则返回 false,并将对象添加到列表中以便稍后重新渲染。即使当前正在更新给定对象,这应该允许我们继续渲染。更新时,按住两个暴击部分。在做游戏逻辑时,按住游戏逻辑暴击部分。如果它已经被持有,那没问题,因为我们有比实际处理器更多的线程。所以如果这个线程被阻塞了,那么另一个线程将只使用 CPU 时间,这不需要管理。
您没有提到您的 Windows 平台限制是什么,但如果您不需要与 Windows Server 2003 或客户端的 Vista 之前的旧版本兼容,您可以使用 InterlockedExchange64() 函数来交换 64 位值。通过将两个 32 位指针打包成一个 64 位对结构,您可以有效地交换两个指针。
有通常的 Interlocked* 变体;InterlockedExchangeAcquire64()、InterlockedCompareExchange64() 等...
如果您需要在 XP 上运行,我会选择关键部分。当竞争的机会很低时,它们的表现相当不错。