【问题标题】:Java Multithreading Network Connection Performance AdviceJava 多线程网络连接性能建议
【发布时间】:2020-07-07 19:04:34
【问题描述】:

我需要同时打开大量网络连接(!)并尽可能快地传输数据。成千上万的连接。现在,我对每个连接都有一个线程,并从该连接的 Inputstream 中读取字符。 而且我强烈怀疑数千个线程之间的 CPU/切换可能会在这里带来一些性能问题,即使服务器真的很慢(低两位数 KB/s),因为我已经观察到吞吐量甚至不接近与线程数成正比。 所以想请教一些有并行编程经验的程序员: 是否值得重写整个程序,以便一个线程以循环方式从多个 InputStream 中读取?如果有加速,那值得编程吗?每个线程有多少个连接?或者您有其他想法可以非常快速地从多个网络输入流中读取数据?

如果我没有读到一个字符,服务器会等到我读完后再发送下一个字符吗?如果我的线程正在休眠怎么办?

【问题讨论】:

    标签: java multithreading performance network-programming java-stream


    【解决方案1】:

    按字符阅读

    您知道数据是以数据包的形式传输的吗?一次读取一个字符是非常低效的。每次读取都必须遍历从程序到操作系统中的网络堆栈的所有层。您应该尝试一次读取一个完整的数据段。

    如果我没有读到一个字符,服务器会等到我读完后再发送下一个字符吗?如果我的线程正在休眠怎么办?

    这就是为什么操作系统有一个用于输入数据的缓冲区,也称为窗口。当 TCP 段到达时,它们被放入接收缓冲区。当您的程序请求从套接字读取时,操作系统会从接收缓冲区返回数据。如果接收缓冲区已满,则数据包丢失,必须重新发送。

    有关 TCP 工作原理的更多信息,请参阅https://beej.us/guide/bgnet/ 维基百科相当不错但相当密集 https://en.m.wikipedia.org/wiki/Transmission_Control_Protocol

    是否值得重写整个程序,以便一个线程以循环方式从多个 InputStream 中读取?如果有加速,那值得编程吗?

    您所描述的需要从阻塞 I/O 转移到非阻塞 I/O。非阻塞将需要更少的系统资源,但要正确有效地实现要困难得多。所以除非你有紧迫的理由,否则不要这样做。

    【讨论】:

    • 感谢您的建议。我会尝试绕过 charwise 阅读并尝试使用 BufferedReader。我在 while 循环中使用 InputStream.read() 方法读取它们。 StackOverflow 上的一些问题说它仍然非常快。当有可用的“非阻塞 I/O”时,不仅从 BufferedReader 读取吗?如果我只使用我的 BufferedReaders 的 LinkedList,读取每一个直到它为空,然后转到下一个?还是效率低下?我会先试试 BufferedReader 的东西
    • BufferedReader 将尝试一次读取更大的数据块,因此即使您一次读取一个字符,您也将获得最大的好处。它无法告诉您新数据何时到达 - 这就是您需要非阻塞 io 并使用其他答案提到的选择器的地方。我猜你正在考虑这样的事情stackoverflow.com/questions/20552223/…
    【解决方案2】:

    对于操作系统调度程序、内存管理单元、缓存来说,数千个线程(和堆栈...)可能太多了...
    您只需要几个线程(每个 CPU 一个)并使用基于 select() 的解决方案 在他们每个人身上。
    看看SelectorServerSocketChannelSocketChannel
    (见https://www.enib.fr/~harrouet/Data/Courses/Memo_Sockets.pdf第30-31页)


    编辑(在 cmets 中的一个问题之后)

    Selector 不仅仅是一个封装在类中的聪明算法。
    它在内部依赖于 select() 系统调用(或等效的, 有很多)。
    操作系统知道一组文件描述符(通信 意味着)它必须观察,并且一旦发生某事(或 其中几个),它唤醒被阻塞的进程(或线程) 在这个选择器上。
    这个想法是尽可能长时间地保持阻塞(以节省资源)和 仅在必须对传入的东西做一些有用的事情时才被唤醒 (有变体)数据。

    在您当前的实现中,您使用了数千个线程 全部被 read()/recv() 操作阻止,因为你不知道 事先知道哪个连接将是下一个交付东西的连接。
    另一方面,对于基于select() 的实现,单个 线程可以被阻止同时观看许多个连接 但只会对刚刚交付的少数几个做出反应 数据。

    所以我建议您启动一个由少数线程组成的池(例如,每个 CPU 一个),并在主程序接受新的传入时立即 连接它选择其中一个(您可以为每个 其中)以使其负责这个新的连接。
    当然,所有这些都需要适当的同步,并且可能 一个技巧(例如选择器中的特殊文件描述符) 为了在分配新连接时唤醒阻塞的线程。

    【讨论】:

    • 好的,感谢您的建议,选择器听起来很有用。我想它比从多个流中读取的自写类更快?
    • @Tobs40 这与操作系统有关。答案已编辑以进一步解释。
    • AHHHH 好的,谢谢 :-) 这听起来是一个不错的加速!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-03
    • 1970-01-01
    • 2019-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多