【问题标题】:How can I pass messages to another thread without using a blocking queue?如何在不使用阻塞队列的情况下将消息传递给另一个线程?
【发布时间】:2011-11-06 23:12:55
【问题描述】:

我有一个非常简单的服务器(使用 kryonet)。客户端仅存储汽车的当前状态(x、y、角度等)并发送加速和转向请求。

服务器正在接收请求并将它们添加到物理线程耗尽、读取和更新的 ArrayBlockingQueue。

当添加另一个玩家时,游戏速度会减慢近一倍。我已经排除了很多事情(我将所有更新和包发送限制在 60Hz。)

我怀疑使用阻塞队列会阻塞太多,导致速度变慢。

如何将客户端请求发送到物理线程而不会出现阻塞问题?

【问题讨论】:

  • 让我直截了当地说:您想使用阻塞队列每秒传递 60 条消息,并认为这是瓶颈??
  • 我不明白你的问题,我对屏蔽列表很陌生,所以请不要这样做。
  • 如果队列没有填满,那么你没有阻塞。你的 CPU 是否在 100% 运行?当您添加一个时,您一开始有多少玩家?
  • CPU 保持在 55% 左右(即使在添加玩家时也是如此)。 1 个玩家目前是 5 个物理体。一开始我有 1 个玩家,然后我运行另一个客户端连接到服务器(所以我有 10 个身体模拟)。当我这样做时,汽车的加速速度是原来的一半。
  • 您在运行多核处理器吗?如果是这样,物理线程将被限制为单个核心。它可以消耗 100% 的内核,这将占双核 CPU 的 50%。

标签: java concurrency box2d slowdown kryo


【解决方案1】:

我怀疑使用阻塞队列会阻塞太多,导致速度变慢。

你怀疑错了。以下测试程序通过 ArrayBlockingQueue 推送 100 万个整数:

public class ArrayBlockingQueuePerfTest {
    int maxi = 1000000;

    ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(1000,
            true);

    Thread sender = new Thread("sender") {
        public void run() {
            try {
                for (int i = 0; i < maxi; i++) {
                    queue.offer(i, 1, TimeUnit.SECONDS);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
    };
    Thread receiver = new Thread("receiver") {
        public void run() {
            try {
                int count = 0;
                long sum = 0;
                while (count < maxi) {
                    sum += queue.poll(1, TimeUnit.SECONDS);
                    count++;
                }
                System.out.println("done");
                System.out.println("expected sum: " + ((long) maxi) * (maxi - 1) / 2);
                System.out.println("actual sum:   " + sum);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
    };

    public ArrayBlockingQueuePerfTest() {
        sender.start();
        receiver.start();
    }

    public static void main(String[] args) {
        new ArrayBlockingQueuePerfTest();
    }
}

在我的笔记本电脑上,它会在几秒钟后终止。因此,无论您的性能瓶颈在哪里,它都不是 ArrayBlockingQueue,它可以处理比您需要的吞吐量至少高 3 个数量级的吞吐量。换句话说,即使您找到了一种完全不占用执行时间的线程通信方法,也最多只能将您的程序加速 0.1%。

为这个和所有其他性能问题吸取教训:解决现有代码中的任何性能问题的第一步是衡量代码的哪个部分很慢,通常情况下,它不在哪里一个期望。分析器大大简化了这项任务。

【讨论】:

  • 我已经通过 JVM 分析器分析了代码,我有很大的日志,但它们目前什么也没显示。添加第二个玩家时没有区别,没有秒杀。我现在怀疑问题不在于服务器,而在于客户端。不过非常感谢您的回复。
【解决方案2】:

您可以使用中断器(环形缓冲区),这是一种实现队列的无锁机制。见:

【讨论】:

  • 使用环形缓冲区(破坏者的核心)将消除并发锁定所花费的少量时间,但我无法想象时间会导致 50% 的减速,每个额外的 60 条消息第二。
【解决方案3】:

我发现了错误。我需要以不同的方式限制物理模拟(不是使用 world.step() 函数,而是限制调用的频率)。是这样的。

while(true)
{
    delta = System.nanoTime() - timer;

    if(delta >= 16666666) // 60 Hz
    {
        world.step(1.0f, 6, 2);

        processAndUpdateYourData();

        timer = System.nanoTime();
    }
}

然后我需要调整所有物理数字,使它们在这种配置下感觉自然。

【讨论】:

    【解决方案4】:

    您的问题以实现为前提——您最好问“为什么我的代码这么慢?”。

    阻塞队列是实现生产者/消费者模式的最有效方式。

    我会添加更多消费者线程 - 尝试添加与处理器内核一样多的消费者线程 - 即 Runtime.getRuntime().availableProcessors()

    【讨论】:

    • 我不能向服务器端添加更多线程,因为客户端数据包的接收器是由 kryonet 库实现的。并且物理线程需要1个线程来模拟世界。
    【解决方案5】:

    确保您创建的队列有足够的可用插槽来满足所有客户端的需求。如果队列因为客户端太多而变满,它们会在尝试插入命令时阻塞。如果这不是问题,则意味着您的物理(消费者)线程没有跟上请求,您需要确保它获得更多处理时间。

    【讨论】:

    • 我可以查看队列是否太满。它将引发异常并使客户端崩溃(当前)。目前还没有发生。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多