《环形队列》游戏高《TPS》模式下减少cpu线程切换
序言
什么高TPS?QPS,其实很多人都知道,还有人说大数据,大流量这些关键词夜以继日的出现在我们眼前;
针对高TPS,QPS这些词汇还有一个次可能比较陌生那就是CCU,tps,qps接受满天飞,CCU在游戏服务端出现比较多,
一个运营(SP)如果问研发(CP)你们游戏承载是多少?通常他们想知道,你们能承载多少玩家在线,并且能承载每个玩家在一秒内有多少个操作;
通常,MMO的RPG类游戏,FPS类游戏,对玩家同时操作要求都相对较高,比如团战,这时候玩家的操作是极具频繁的;
在游戏界很多人都知道传统的页游,或者备份手游,在服务器端,设计是就是以不同的地图切换来做整个世界场景,
每一个场景通过传送门切换到下一章地图;在传统游戏做法就是每一张地图就是一个线程,这个在游戏界广为流传的代码《秦美人》模式;
这样做的好处就是每一个地图都是单独的线程,自己管理自己范围的事情,不会冲突,但是理论终究是理论,
实际在线运营情况就是,某些地图比如《主城》《副本入口》《活动入口》这些地方聚集了大量的玩家,在某些低等级地图或者没什么任务和装逼产出的地图玩家少的可怜甚至没有;
在传统游戏,比如最早的盛大代理的《冒险岛》业内家喻户晓的《秦美人》代码都有分线这么一说就是为了解决在一张地图人太多,一个线程处理不了的问题;
这种传统模式就是一句话,忙得忙死,闲的闲死,一句套用现在皮友的一句话,涝的涝死,旱的旱死;
那么应运而生的是什么
没错就是我们今天要讲的环形排队队列;可能听起来有点绕口,
其目的是什么呢?通过队列模型,达到线程恭喜,不再有专有线程,减少线程数量,线程做什么事情由队列说了算;
我们通常队列是这样的,先进先出,
排队队列是什么情况呢?
就是队列里面的某一项,当从队列里面获取到这一项的时候,发现这一项本身不是一个任务而是一个队列;
然后取出这一项队列里面的第一项来执行,
一般来讲我们需要的队列基本也就是两层就够了,
当然你们如果需要三层,或者更多,你们可以稍加改动我们后面的代码;
翠花上代码
队列枚举
1 package com.ty.test.queue;
2
3 /**
4 * 队列key值
5 *
6 * @author: Troy.Chen(失足程序员, 15388152619)
7 * @create: 2021-04-06 11:19
8 **/
9 public enum QueueKey {
10 /**
11 * 默认队列是不区分,顺序执行
12 */
13 Default,
14 /**
15 * 登录任务处理
16 */
17 Login,
18 /**
19 * 联盟,工会处理
20 */
21 Union,
22 /**
23 * 商店处理
24 */
25 Shop,
26 ;
27
28 }
队列任务
1 package com.ty.test.queue;
2
3 import java.io.Serializable;
4
5 /**
6 * @author: Troy.Chen(失足程序员, 15388152619)
7 * @create: 2021-04-06 11:35
8 **/
9 public abstract class TyEvent implements Serializable {
10
11 private static final long serialVersionUID = 1L;
12
13 private QueueKey queueKey;
14
15 public abstract void run();
16
17 public TyEvent(QueueKey queueKey) {
18 this.queueKey = queueKey;
19 }
20
21 public QueueKey getQueueKey() {
22 return queueKey;
23 }
24 }
最关键的代码来了,这个是主队列,
1 package com.ty.test.queue;
2
3 import java.io.Serializable;
4 import java.util.HashMap;
5 import java.util.concurrent.LinkedBlockingQueue;
6 import java.util.concurrent.TimeUnit;
7 import java.util.concurrent.atomic.AtomicInteger;
8 import java.util.concurrent.locks.ReentrantLock;
9
10 /**
11 * 队列
12 *
13 * @author: Troy.Chen(失足程序员, 15388152619)
14 * @create: 2021-04-06 11:14
15 **/
16 public class TyQueue implements Serializable {
17
18 private static final long serialVersionUID = 1L;
19 /*同步锁保证队列数据的准确性*/
20 protected ReentrantLock reentrantLock = new ReentrantLock();
21 /*子队列容器*/
22 protected HashMap<QueueKey, TySubQueue> subQueueMap = new HashMap<>();
23
24 /*队列容器*/
25 protected LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>();
26 /*队列里面包括子队列任务项*/
27 protected final AtomicInteger queueSize = new AtomicInteger();
28
29 /**
30 * 添加任务
31 *
32 * @param obj
33 */
34 public void add(TyEvent obj) {
35 reentrantLock.lock();
36 try {
37 if (obj.getQueueKey() == QueueKey.Default) {
38 /*默认模式直接加入队列*/
39 queue.add(obj);
40 } else {
41 TySubQueue subQueue = subQueueMap.get(obj.getQueueKey());
42 if (subQueue == null) {
43 subQueue = new TySubQueue(obj.getQueueKey());
44 subQueueMap.put(obj.getQueueKey(), subQueue);
45 }
46 /*有排队情况的,需要加入到排队子队列*/
47 subQueue.add(obj);
48 /*这里是关键,*/
49 if (!subQueue.isAddQueue()) {
50 subQueue.setAddQueue(true);
51 /*如果当前子队列不在队列项里面,需要加入到队列项里面去*/
52 queue.add(subQueue);
53 }
54 }
55 /*队列的数据加一*/
56 queueSize.incrementAndGet();
57 } finally {
58 reentrantLock.unlock();
59 }
60 }
61
62
63 /**
64 * 获取任务
65 *
66 * @return
67 * @throws InterruptedException
68 */
69 public TyEvent poll() throws InterruptedException {
70 Object poll = this.queue.poll(500, TimeUnit.MILLISECONDS);
71 if (poll instanceof TySubQueue) {
72 try {
73 reentrantLock.lock();
74 TySubQueue subQueue = (TySubQueue) poll;
75 poll = subQueue.poll();
76 } finally {
77 reentrantLock.unlock();
78 }
79 }
80 if (poll != null) {
81 /*执行减一操作*/
82 this.queueSize.decrementAndGet();
83 return (TyEvent) poll;
84 }
85 return null;
86 }
87
88 /**
89 * 当任务执行完成后操作
90 *
91 * @param obj
92 */
93 public void runEnd(TyEvent obj) {
94 reentrantLock.lock();
95 try {
96 if (obj.getQueueKey() != QueueKey.Default) {
97 TySubQueue subQueue = subQueueMap.get(obj.getQueueKey());
98 if (subQueue != null) {
99 if (subQueue.size() > 0) {
100 /*这个时候需要把队列重新添加到主队列*/
101 queue.add(subQueue);
102 } else {
103 /*当子队列空的时候,标识队列已经不在主队列里面,等待下次加入新任务*/
104 subQueue.setAddQueue(false);
105 }
106 }
107 }
108 } finally {
109 reentrantLock.unlock();
110 }
111 }
112 }
子队列,也就是排队队列的关键所在
1 package com.ty.test.queue;
2
3 import java.io.Serializable;
4 import java.util.LinkedList;
5
6 /**
7 * 下级队列
8 *
9 * @author: Troy.Chen(失足程序员, 15388152619)
10 * @create: 2021-04-06 11:14
11 **/
12 public class TySubQueue extends LinkedList<TyEvent> implements Serializable {
13
14 private static final long serialVersionUID = 1L;
15
16 private final QueueKey queueKey;
17 private boolean addQueue = false;
18
19 public TySubQueue(QueueKey queueKey) {
20 this.queueKey = queueKey;
21 }
22
23 public QueueKey getQueueKey() {
24 return queueKey;
25 }
26
27 public boolean isAddQueue() {
28 return addQueue;
29 }
30
31 public TySubQueue setAddQueue(boolean addQueue) {
32 this.addQueue = addQueue;
33 return this;
34 }
35
36 @Override
37 public String toString() {
38 return "{" + "queueKey=" + queueKey + ", size=" + size() + '}';
39 }
40 }
测试一下结果
1 package com.ty.test.queue;
2
3 /**
4 * @author: Troy.Chen(失足程序员, 15388152619)
5 * @create: 2021-04-06 11:46
6 **/
7 public class Test {
8
9 public static final TyQueue queue = new TyQueue();
10
11 public static void main(String[] args) {
12 queue.add(new TyEvent(QueueKey.Default) {
13 @Override
14 public void run() {
15 System.out.println(Thread.currentThread().getId() + ", 1");
16 }
17 });
18 queue.add(new TyEvent(QueueKey.Default) {
19 @Override
20 public void run() {
21 System.out.println(Thread.currentThread().getId() + ", 2");
22 }
23 });
24 queue.add(new TyEvent(QueueKey.Default) {
25 @Override
26 public void run() {
27 System.out.println(Thread.currentThread().getId() + ", 3");
28 }
29 });
30
31 T t1 = new T();
32 T t2 = new T();
33 T t3 = new T();
34 t1.start();
35 t2.start();
36 t3.start();
37 }
38
39 public static class T extends Thread {
40
41 @Override
42 public void run() {
43 while (!Thread.currentThread().isInterrupted()) {
44 TyEvent poll = null;
45 try {
46 poll = queue.poll();
47 if (poll != null) {
48 /*执行任务*/
49 poll.run();
50 }
51 } catch (InterruptedException interruptedException) {
52 Thread.currentThread().interrupt();
53 } catch (Throwable throwable) {
54 throwable.printStackTrace(System.out);
55 } finally {
56 if (poll != null) {
57 /*当然任务执行完成后*/
58 queue.runEnd(poll);
59 }
60 }
61 }
62 }
63
64 }
65
66 }
我们用三个线程测试一下,在没有排队情况下执行输出
我们可以看到三个任务分别有三个线程执行了;
接下来我们在队列里面再额外加入三个登录排队队列
1 queue.add(new TyEvent(QueueKey.Login) {
2 @Override
3 public void run() {
4 System.out.println(Thread.currentThread().getId() + ", Login 1");
5 }
6 });
7 queue.add(new TyEvent(QueueKey.Login) {
8 @Override
9 public void run() {
10 System.out.println(Thread.currentThread().getId() + ", Login 2");
11 }
12 });
13 queue.add(new TyEvent(QueueKey.Login) {
14 @Override
15 public void run() {
16 System.out.println(Thread.currentThread().getId() + ", Login 3");
17 }
18 });
再看看输出情况,
很明显的可以看到我们加入到登录队列的任务,又同一个线程顺序执行的;
总结
排队队列就是为了让同一类型任务顺序执行或者叫多任务操作同一个对象的时候减少加锁 带来的额外开销,减少线程等待的时间;
更合理的利用的线程。避免涝的涝死,旱的旱死;
我要订一个小目标
《环形队列》游戏高《TPS》模式下减少cpu线程切换的更多相关文章
- 001/Nginx高可用模式下的负载均衡与动静分离(笔记)
Nginx高可用模式下的负载均衡与动静分离 Nginx(engine x)是一个高性能的HTTP和反向代理服务器,具有内存少,并发能力强特点. 1.处理静态文件.索引文件以及自动索引:打开文件描述符缓 ...
- Spark -14:spark Hadoop 高可用模式下读写hdfs
第一种,通过配置文件 val sc = new SparkContext() sc.hadoopConfiguration.set("fs.defaultFS", "hd ...
- JMeter命令模式下动态设置线程组和持续时间等动态传参
背景: 1.当通过JMeter的图像化界面运行性能压测或者场景时候,JMeter界面很容易导致界面卡死或者无响应的情况(20个线程数就会卡死) 现象如下:
- IDEA多线程下多个线程切换断点运行调试的技巧
多线程调试设置可以参考:http://www.cnblogs.com/leodaxin/p/7710630.html 1 断点设置如图: 2 测试代码,然后进行debug package com.da ...
- java ReentrantLock结合条件队列 实现生产者-消费者模式 以及ReentratLock和Synchronized对比
package reentrantlock; import java.util.ArrayList; public class ProviderAndConsumerTest { static Pro ...
- hadoop和hbase高可用模式部署
记录apache版本的hadoop和hbase的安装,并启用高可用模式. 1. 主机环境 我这里使用的操作系统是centos 6.5,安装在vmware上,共三台. 主机名 IP 操作系统 用户名 安 ...
- Jackson高并发情况下,产生阻塞
情况:在高并发情况下,查看线程栈信息,有大量的线程BLOCKED. 从线程栈得知,线程栈中出现了阻塞,锁在了com.fasterxml.jackson.databind.ser.SerializerC ...
- MQ在高并发环境下,如果队列满了,如何防止消息丢失?
1.为什么MQ能解决高并发环境下的消息堆积问题? MQ消息如果堆积,消费者不会立马消费所有的消息,不具有实时性,所以可以解决高并发的问题. 性能比较好的消息中间件:Kafka.RabbitMQ,Roc ...
- 卡卡游戏引擎之MVC模式下的事件处理
前言 在前一篇文章 卡卡游戏引擎快速入门中提到了卡卡游戏引擎采用mvc的开发模式,这里相信介绍一下引擎在mvc模式下是如何做到低耦合的事件处理的. 在卡卡编辑器中选择一个节点,然后在左侧工具栏中切换到 ...
随机推荐
- search cascade select & AntD
search cascade select & AntD Antd https://ant.design/components/cascader-cn/#components-cascader ...
- NGK Global首尔站:内存是未来获取数字财富的新模式
近日,NGK路演在NGK韩国社区的积极举办下顺利落下帷幕.此次路演在首尔举行,在活动当天,NGK的核心团队成员.行业专家.投资银行精英.生态产业代表和数百名NGK韩国社区粉丝一起参加NGK Globa ...
- 05.其他创建numpy数组的方法
>>> import numpy as np >>> np.zeros(10,dtype=int) array([0, 0, 0, 0, 0, 0, 0, 0, 0 ...
- 如何用python自动编写《赤壁赋》word文档
目录 前言 安装-python-docx 一.自动编写<赤壁赋> 准备数据 新建文档 添加标题 添加作者 添加朝代 添加图片 添加段落 保存word文档 二.自动提取<赤壁赋> ...
- PythonPEP8 风格规范指南
PEP是Python Enhancement Proposal的缩写,通常翻译为"Python增强提案".每个PEP都是一份为Python社区提供的指导Python往更好的方向发展 ...
- Linux文件常用指令
目录 Linux文件常用指令 1.pwd 显示当前目录 2.cd 切换目录 3.mkdir 创建目录 4.touch 修改或创建文件 5.ls 显示目录下的内容 6.cat 查看文件信息 7.echo ...
- 低功耗蓝牙 ATT/GATT/Service/Characteristic 规格解读
什么是蓝牙service和characteristic?如何理解蓝牙profile? ATT和GATT两者如何区分?什么是attribute? attribute和characteristic的区别是 ...
- 用量子计算模拟器ProjectQ生成随机数,并用pytest进行单元测试与覆盖率测试
技术背景 本文中主要包含有三个领域的知识点:随机数的应用.量子计算模拟产生随机数与基于pytest框架的单元测试与覆盖率测试,这里先简单分别介绍一下背景知识. 随机数的应用 在上一篇介绍量子态模拟采样 ...
- 力扣208. 实现 Trie (前缀树)
原题 以下是我的代码,就是简单的字符串操作,可以ac但背离了题意,我之前没接触过Trie 1 class Trie: 2 3 def __init__(self): 4 ""&qu ...
- 【转载】Android的事件分发(dispatchTouchEvent),拦截(onInterceptTouchEvent)与处理(onTouchEvent)
出处:https://blog.csdn.net/caifengyao/article/details/65437695 在Android中,View的结构是树状的,所以,当触发触摸事件的时候,其事件 ...