运行线程需要很多 CPU



我正在使用下面的客户端线程连接到我的NIO服务器。

    class RunnableDemo implements Runnable {
    private Thread t;
    private String threadName;
    InetAddress host = null;
    int port = 9090;
    RunnableDemo(String name) {
        threadName = name;
        System.err.println("Creating " + threadName);
    }
    public void run() {
        System.err.println("Running " + threadName);
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress(host, port));
            while (!socketChannel.finishConnect())
                ;
            System.out.println("Thread " + threadName + " Connected");
            while (true) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                if (socketChannel.read(buffer) != 0) {
                    buffer.flip();
                    byte[] bytes = new byte[buffer.limit()];
                    buffer.get(bytes);
                    System.out.println(threadName+ ":" + new String(bytes));
                    buffer.clear();
                }
            }
        } catch (Exception e) {
            System.out.println("Thread " + threadName + " interrupted.");
            e.printStackTrace();
        }
        System.out.println("Thread " + threadName + " exiting.");
    }
    public void start() {
        System.out.println("Starting " + threadName);
        try {
            host = InetAddress.getByName("127.0.0.1");
            if (t == null) {
                t = new Thread(this, threadName);
                t.start();
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

这是我的服务器端代码。 当我运行服务器端时,只有 CPU 不超过 5%,但当我为每个线程运行客户端时,CPU 使用率将提高约 20-30%

public class EchoServer {
    private static final int BUFFER_SIZE = 1024;
    private final static int DEFAULT_PORT = 9090;
    private long numMessages = 0;
    private long loopTime;
    private InetAddress hostAddress = null;
    private int port;
    private Selector selector;
    // The buffer into which we'll read data when it's available
    private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
    int timestamp=0;
    public EchoServer() throws IOException {
        this(DEFAULT_PORT);
    }
    public EchoServer(int port) throws IOException {
        this.port = port;
        hostAddress = InetAddress.getByName("127.0.0.1");
        selector = initSelector();
        loop();
    }
    private Selector initSelector() throws IOException {
        Selector socketSelector = SelectorProvider.provider().openSelector();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        InetSocketAddress isa = new InetSocketAddress(hostAddress, port);
        serverChannel.socket().bind(isa);
        serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
        return socketSelector;
    }
    private void loop() {
        for (;true;) {
            try {
                selector.select();
                Iterator<SelectionKey> selectedKeys = selector.selectedKeys()
                        .iterator();
                while (selectedKeys.hasNext()) {
                    SelectionKey key = selectedKeys.next();
                    selectedKeys.remove();
                    if (!key.isValid()) {
                        continue;
                    }
                     // Check what event is available and deal with it
                    if (key.isAcceptable()) {
                        accept(key);
                    } else if (key.isWritable()) {
                        write(key);
                    }
                }
                Thread.sleep(3000);
                timestamp+=3;
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }

        }
    }
    private void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
        socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
        socketChannel.register(selector, SelectionKey.OP_WRITE);
        System.out.println("Client is connected");
    }
    private void write(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer dummyResponse = ByteBuffer.wrap(("ok:" + String.valueOf(timestamp)) .getBytes("UTF-8"));
        socketChannel.write(dummyResponse);
        if (dummyResponse.remaining() > 0) {
            System.err.print("Filled UP");
        }
        System.out.println("Message Sent");
     //   key.interestOps(SelectionKey.OP_READ);
    }
}

一切都很正常。客户端和服务器可以相互查看并通信。为了测试我的代码可以接受多少连接,我创建了上述线程的几个实例,这就是问题所在。

当我通过生成此线程的每个实例来跟踪任务面板(Windows)的性能扇区时,我的 PC(我使用的是 2.6 核 i5 CPU)的 CPU 使用率提高了 30%,通过生成 3 个线程,我的 CPU 使用率约为 100% !!

想知道上面的代码占用了我 30% 的 CPU,这有什么问题。

我可以看到高CPU负载的两个潜在原因。

  1. 您不恰当地使用非阻塞 I/O,(实际上)重复轮询通道以完成连接并读取数据。 在此特定用例中,最好使用阻塞 I/O。 数据吞吐量将(几乎)相同,并且您不会通过轮询浪费 CPU。

    一般来说,非阻塞 I/O 仅在线程有其他事情要做而不是阻塞时才是一个好主意。

  2. 写入System.out也可能使用大量 CPU ...在 JVM 外部。 如果标准输出转到在屏幕上显示它的典型控制台应用程序,那么将文本呈现和绘制到屏幕上的过程......和滚动...可以使用相当数量的 CPU。

看不到任何会导致过度压力的特殊内容,但我建议您在循环中包括短暂的睡眠,以确保您不会占用CPU。这包括从主线程中抢走杯子(这可能是一个服务器,期望完成侦听迎面而来的连接的重要工作)。

我特别会睡在执行外部活动并且预期延迟的地方。

另请参阅 Thread.sleep。

这样做后,我会在监控 CPU、内存、负载等的同时,针对您的原始版本进行全程测试。

相关内容

  • 没有找到相关文章

最新更新