代码之家  ›  专栏  ›  技术社区  ›  theburningmonk

解决集合的并发问题

  •  2
  • theburningmonk  · 技术社区  · 16 年前

    我在一个多玩家同步游戏中同时使用共享集合时遇到了问题。我在Alexey Drobyshevsky关于codeproject的文章中做了一些挖掘,发现了一个整洁的线程安全IEnumerator/IList实现:

    http://www.codeproject.com/KB/cs/safe_enumerable.aspx

    在采用了他的实现之后,我甚至用for/foreach循环替换了共享集合上的所有Linq查询,因为Linq查询仍然使用不安全的IEnumerable。

    http://theburningmonk.com/2010/03/thread-safe-enumeration-in-csharp/

    切换到此安全列表后,我看到的问题要少得多,但在高负载下(80多个线程,所有线程都在不同点从列表中读/写),我仍然看到引发InvalidOperationException:

    元素列表已更改。枚举操作无法继续

    在我的安全列表实现中,我甚至尝试使用ReadWriterLockSlim来代替lock对象,但这也被证明是徒劳的。到目前为止,我得到的另一个建议是,每当线程需要循环遍历该列表时,就克隆该列表。我希望避免每次都克隆列表,因为列表在太多的地方使用,这可能会影响性能,并可能引入其他难以发现的错误。

    考虑到时间的限制,我必须实事求是,如果克隆是解决这个问题的最安全、最快捷的方法,那么我对它没意见,但在采取这一最后的尝试之前,我只是想知道是否有人遇到过类似的事情,能够提供一些建议。

    非常感谢!

    [编辑]以下是有关我所看到的问题的更多信息:

    对于一个“游戏”,最多可以连接100个左右的同步客户端,游戏需要每隔几秒钟向每个连接的客户端发送更新消息,因此每隔几秒钟该游戏需要遍历共享的玩家列表。 除此之外,玩家还可以与游戏互动并与其他玩家聊天,每次收到来自玩家的消息时,游戏都需要重复浏览同一列表并进行广播。 当游戏试图同时向玩家广播消息(读取操作)和许多玩家同时离开/加入(写入操作)时,通常会抛出异常。

    2 回复  |  直到 16 年前
        1
  •  6
  •   Daniel Earwicker    16 年前

    根据你对游戏结构的描述,考虑到只有一个线程可以直接访问玩家列表。使列表对该线程有效地私有。

    基本原理:将数据私有化为一个线程,并让线程通过消息队列进行通信。

    您的基本数据结构是线程安全队列类。已经有几十个这样的例子了(并避免任何声称是“无锁”但线程安全的。只是不值得冒险。)

        2
  •  1
  •   S.Skov    16 年前

    尽管关于重构的其他建议是可行的,但“线程安全”类中的一个错误还是漏掉了(可能还有更多):

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        // instead of returning an usafe enumerator,
        // we wrap it into our thread-safe class
        return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
    }
    

    _inner.GetEnumerator 在构造函数运行之前,因此任何线程都可以自由修改集合,直到您锁定构造函数为止。这是一个很小的时隙,但是有80个线程它就会发生。你得把门锁好 return .. 语句来保护枚举数。

    编辑:也可以在使用相同图案的其他地方编辑。