.net - 多线程是适合我的情况的正确方法吗?

标签 .net multithreading

我目前正在设计一个多客户端/服务器应用程序。我使用的是普通的老式套接字,因为我不需要WCF或类似技术。让我解释一下:这不是客户简单地调用服务的经典情况;所有客户端都可以通过向服务器发送数据包来彼此“交互”,然后服务器将执行一些操作,并可能将应答消息重新发送给一个或多个客户端。尽管可以与WCF一起使用,但应用程序将变得非常复杂,包含数百种不同的消息。

对于每个连接的客户端,我当然使用异步方法来发送和接收字节。我的消息已经完全正常了,一切都很好。除了我正在编写的每一行代码外,由于多线程问题,我的头都被烧死了。由于可以同时连接约200个客户端,因此我选择采用完全多线程的方式:套接字上接收到的每个消息都会在接收到的线程池线程上立即处理,而不是在单个使用者线程上进行处理。

由于每个客户端都可以与其他客户端交互,并且可以与服务器上的共享对象间接交互,因此我必须保护几乎所有可变的对象。首先,我为每个必须保护的资源使用了ReaderWriterLockSlim,但很快注意到服务器应用程序中的总体写入次数大于读取的次数,然后切换到众所周知的Monitor简化代码。

到目前为止,一切都很好。每个资源都 protected ,我必须使用帮助程序类来获取锁及其 protected 资源,因此,如果没有获取锁,就无法使用对象。此外,每个客户端都有自己的锁,一旦从其套接字接收到数据包,便会输入该锁。这样做是为了防止其他客户端在处理某些消息时更改此客户端的状态,这种情况经常发生。

现在,我不仅需要保护资源以防止并发访问。对于某些收藏,我必须使每个客户端与服务器保持同步。我目前正在努力解决的一个棘手的部分是:

  • 我有很多客户。每个客户端都有自己的唯一ID。
  • 当客户端连接时,它必须接收每个已连接客户端的ID,并且必须将新来者的ID通知给每个客户端。
  • 当客户端断开连接时,其他所有客户端都必须知道它,以便其ID对他们而言不再有效。
  • 在给定的时间,每个客户端必须始终具有与服务器相同的客户端集合,以便我可以假定每个人都认识每个人。这样,如果我要向客户端1发送一条消息,告诉它“客户端2已经完成某件事”,我知道它将始终得到正确的解释:客户端1永远不会怀疑“但是客户端2到底是谁?”。

  • 我第一次尝试处理新客户端的连接(我们称其为X)是此伪代码(请记住newClient已在此处锁定):
    lock (clients) {
      foreach (var client in clients) {
        lock (client) {
          client.Send("newClient with id X has connected");
        }
      }
      clients.Add(newClient);
      newClient.Send("the list of other clients");
    }
    

    现在想象一下,在同一时间,另一个客户端发送了一个数据包,该数据包转换为必须广播给每个连接的客户端的消息,伪代码将是这样的(请记住,当前客户端-我们称之为Y-是已经锁定在这里):
    lock (clients) {
      foreach (var client in clients) {
        lock (client) {
          client.Send("something");
        }
      }
    }
    

    此处出现一个明显的死锁:在一个线程X上已锁定,已输入clients锁,开始遍历客户端,并且一时必须获得Y的锁...第二个线程上已获取了它的锁,它本身正在等待客户集合锁将被释放!

    这不是服务器应用程序中唯一的这种情况。还有其他集合必须与客户端保持同步,客户端上的某些属性可以由另一个属性更改,等等。我尝试了其他类型的锁,无锁机制以及许多其他操作。当我为了安全起见使用过多的锁时,要么出现了明显的死锁,否则就出现了明显的比赛条件。当我最终在两者之间找到一个很好的中间点时,通常会出现非常微妙的竞争条件/死锁和其他多线程问题...我的头很快就疼了,因为对于我正在编写的任何一行代码,我都有审查几乎整个应用程序,以确保任何数量的线程都能正常运行。

    所以这是我的最后一个问题:您将如何解决这个特殊情况,更一般的情况,更重要的是:我在这里不是走错路了吗?我对.NET框架,C#,简单并发或一般算法没有什么问题。不过,我在这里迷路了。我知道我只能使用一个线程来处理传入的请求,一切都会好起来的。但是,如果有更多的客户,这根本无法很好地扩展……但是我正在越来越多地考虑采用这种简单的方法。你怎么认为?

    在此先感谢您,StackOverflow的人们花了一些时间阅读这个巨大的问题。如果我想获得一些帮助,我真的必须解释整个情况。

    最佳答案

    我在先前的评论中提到了Erlang,并在另一个评论中将消息处理排队。 Erlang从头开始设计,以支持高度并发,无共享,消息传递样式的系统。

    http://en.wikipedia.org/wiki/Erlang_(programming_language)

    尽管我从来没有用过它,但是我已经读过《 Programming Erlang》这本书,并且真的很喜欢它所体现的并发消息传递方法的简单之处。在完成了大量复杂的多线程开发之后,我可以体会到Erlang试图解决的挑战,即共享资源和同步的复杂性。

    有一个C#项目试图体现Erlang-Retlang的概念:

    http://code.google.com/p/retlang/wiki/GettingStarted

    从未使用过它,但是消息传递方法绝对是一种不错的方法,并且可能非常适合您要实现的目标。

    关于.net - 多线程是适合我的情况的正确方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2885011/

    相关文章:

    c# - 如何在设计时从我的 WPF 应用程序获取应用程序的目录?

    c# - 升级到 Excel 2007 - 应用程序仍使用 2003 互操作

    c# - 如何异步调用我的 WCF 服务?

    c# - 在 C# 中设置模块化程序的最佳方式

    java - Java 中的并行任务

    C# - 可以在单独的线程中安全地拥有一个拥有的表单吗?

    .net - 如何以编程方式刷新 Windows Mobile 中的 DNS 缓存?

    c# - 为什么我会收到此错误 :"Cross-thread operation not valid: Control lbFolders accessed from a thread other than the thread it was created on."?

    java - Schedulers.computation() 在 MainThread 上执行

    ruby - 是否有 ruby​​ 的 'standard' 读/写锁实现?