游戏线程池的设计0-转自

游戏线程池的设计

在前面的章节中,我们一再提到,游戏的业务逻辑复杂,
很多数据都是放在内存中进行处理的,但是,仅仅放在内存中还不能完全满足要求。

我举个例子,房间 A 里面的四个玩家打牌,如果使用普通的 Java 线程池处理这个房间的所有信息,就会带来新的问题:

1 所有消息对于房间信息的修改,需要使用锁,对性能有一定的影响;
2 使用锁之后,业务实现者要时时考虑锁的使用问题,增加了业务开发的难度
3 有序性,我们知道 Java 线程池中任务的流转过程,先看有没有达到核心线程数,再看队列有没有满,最后看有没有达到最大线程数,我举个极端的例子,如果队列满了,此时,发来一条消息,它会去尝试创建一个新的线程(未达最大线程数)来处理当前消息,这导致该房间之前的消息可能还在队列中未处理,而这条消息却先处理了,这明显不符合要求。

基于以上三点原因,使用 Java 线程池肯定是不能满足我们的要求了,那么,使用 Netty 的线程池是否可以呢?

所以,我们需要重新设计一种线程池:不加锁且消息有序,这要怎么实现呢?

我们前面提到过一个名词:房间制,那么,是不是把同一个房间的消息发到同一个线程进行处理就可以了呢?
答案确实是这样的,但是实现起来并没有那么简单。
首先,使用 Java 自带的线程池肯定是不行的了。
其次,使用 Netty 的线程池,通过前面关于 Netty 线程池的源码剖析,我们知道,在 Netty 中,有两种选择线程的方式,即 PowerOfTwoEventExecutorChooser 和 GenericEventExecutorChooser,然而,它们的内部实现并没有本质上的区别,都是通过轮询的方式调用线程池中的线程来处理任务,所以,乍看之下,也不满足我们的要求。

最后,只能自己来实现了?自己实现的话,也是每个线程维护自己的队列,且把同一个房间的消息都到同一个线程的队列中,这其实跟 Netty 还是有些像的,而且,自己实现的线程池,在稳定性、安全性等方面的考量,也是一个非常重要的问题,所以,我们能不能对 Netty 的线程池进行改造,拿过来即用呢?

通过前面的分析,可以发现,其实 Netty 的线程池本身是满足我们的要求的,关键在于选择线程的方式这里,所以,改造的要点就在这里,我们需要自己实现一种线程选择的方式,比如,按房间号去选择线程。

private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    private final AtomicInteger idx = new AtomicInteger();
    private final EventExecutor[] executors;

    PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
        this.executors = executors;
    }

    @Override
    public EventExecutor next() {
        return executors[idx.getAndIncrement() & executors.length - 1];
    }
}

在调用 next () 方法选择线程的时候,似乎不能传递额外的参数了,但是,别忘了,我们还有终极杀器 —— 线程本地变量,在调用 next () 方法之前,可以把房间号存储在 FastThreadLocal 中,这样在 next () 中就能获取到这个房间号,然后,就可以使用房间号来做路由了,比如按房间号取模,完美 ^^

游戏线程池的实现
参考 DefaultEventExecutorChooserFactory 的代码,我们实现一个自己用的 MahjongEventExecutorChooserFactory,并将其运用到自定义的线程池 MahjongEventExecutorGroup 中:

public class MahjongEventExecutorGroup extends MultithreadEventExecutorGroup {

    private MahjongEventExecutorGroup(int nThreads) {
        // 使用自定义的选择器工厂
        super(nThreads, null, new MahjongEventExecutorChooserFactory(), null);
    }

    @Override
    protected EventExecutor newChild(Executor executor, Object... args) throws Exception {
        return new DefaultEventExecutor(this, executor);
    }
    // 工厂类
    private static class MahjongEventExecutorChooserFactory implements EventExecutorChooserFactory {

        @Override
        public EventExecutorChooser newChooser(EventExecutor[] executors) {
            return new MahjongEventExecutorChooser(executors);
        }
        // 真正使用的选择器
        private static class MahjongEventExecutorChooser implements EventExecutorChooserFactory.EventExecutorChooser {

            private final AtomicInteger idx = new AtomicInteger();
            private final EventExecutor[] executors;

            MahjongEventExecutorChooser(EventExecutor[] executors) {
                this.executors = executors;
            }

            @Override
            public EventExecutor next() {
                // 从上下文中取出当前的房间id
                Long roomId = MahjongContext.currentContext().getCurrentRoomId();
                long id;
                if (roomId != null) {
                    id = roomId;
                } else {
                    // 没获取到房间号的消息轮徇扔到业务线程池中处理
                    // 他们往往跟房间信息没啥关系,比如登录请求
                    id = idx.getAndIncrement();
                }
                // 根据id取模选择线程执行
                return executors[(int) (id & executors.length - 1)];
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值