强化学习-深度蒙特卡洛算法(Deep Monte-Carlo)解决骰子游戏“吹牛”

news2024/11/30 10:44:10

一、算法简介

        深度蒙特卡洛算法是一种使用深度神经网络来进行蒙特卡洛估计的强化学习算法,它最早于2020年在《DouZero: Mastering DouDizhu with Self-Play Deep Reinforcement Learning》被提出用于解决斗地主问题。

        深度蒙特卡洛算法使用深度网络拟合每个时刻,智能体状态和采取每种动作的价值函数,即Q value,所以其属于value base 类方法。

        1.网络结构

        以斗地主问题为例,编码网络由两部分组成,1.对(合法的)出牌动作的编码。2.对玩家手牌和历史出牌信息的编码。

        出牌动作编码网络用于对出牌动作对应的矩阵进行特征提取,它的输入是一种出牌动作对应的矩阵,这种出牌动作可以来自当前智能体的所有合法的出牌组合,这种设计的优点有 1.使得模型不需要动作掩码就能对非法动作进行屏蔽,2.适用于于较大动作空间的情况,并且不会因为动作空间的增大而导致参数量明显增加。3.模型能对智能体做出的动作有更深的理解,因为动作也具有了丰富特征,而不仅仅一种one-hot向量。这种对动作的深刻理解有益于其对未曾见过的动作的估计的泛化。如3KKK的牌型很好,即使未见过动作3JJJ,其也能对动作价值有良好的评估。

        玩家手牌和历史出牌信息的编码网络用于编码玩家当前持有的手牌,和其余玩家已经打出的手牌,记住已经打出的手牌对于斗地主之类部分可观测的马尔可夫决策过程有重要意义。

        两种编码网络对信息进行提取后最终会合并输入一个全连接网络再次进行深层特征提取后最终输出一个形如 1x1 的值,由于编码网络中已经含有状态S和可能采取的动作A,故编码网络输出的值可以作为当前状态s下采取动作a的Q值。遍历所有合法的动作a,就可得agent在当前状态下采取任意动作的Q值。

        

         2.算法更新方式

        2.1采样

        DMC算法采样类似于DQN,也使用epsilon-greedy方法:以epsilon 的概况v采取随机动作,以1-epsilon的概率采取Q值最大的动作。

        2.2损失函数

        不同于DQN的每一步获得奖励,自举的对Q值的估计,DMC算法只在每局结束时获得所有的奖励。DMC使用均方差 MSE loss对Q值进行更新。这使得DMC的Q值是无偏估计,但同时也有着高方差的问题,在原文中通过并行采样缓解。

二 使用DMC解决“吹牛”游戏

        1.游戏规则

        两个玩家、每人三个骰子、游戏开始后,两人同时投骰子,每个人只能看自己的点数。根据自己的点数,轮流喊出自己认为的两人骰子的最大公约数。如先手喊出 2个6,代表两人的六个骰子中至少有两个6点的骰子,后手要么喊出比先手更大的公约数,如3个6,4个5等,要么选择不相信,然后进入结算阶段,如果结算阶段发现两个人骰子中至少有2个6点骰子,则先手胜如果没有则后手胜利。

        另外,点数为1的骰子可以作为任意点数参与结算。

        2.状态空间和动作空间

        动作空间很明确,从1个1 到6个6,外加一个不相信,共37个动作

        状态空间,在未结算阶段,由自己的点数和历史动作组成,在结算阶段由自己和对方点数和历史动作组成。

        3.环境代码

        可以适配DMC算法的吹牛环境代码如下,实现了 reset step等方法,可以对动作和状态编码,根据结局胜负给双方正负奖励。

class BoastingEnv:
    def __init__(self, objective):
        self.env_id_ran = random.randint(0, 100)
        self.objective = objective
        self.last_action = None
        self.agent_names = ["firsthand", "secondhand"]  # AGENT_NAMES#['agent0','agent1']
        self.acting_player_position = None
        self.player_0 = [1, 5, 2]#[random.randint(0, 5) for i in range(3)]
        self.player_1 = [0, 5, 1]#[random.randint(0, 5) for i in range(3)]
        self.action_history = np.zeros((4, 6, 7))  # ((6 * 6 + 1, 2))
        self.end_action = np.zeros((6, 7))
        self.end_action[0, 6] = 1
        # self.action_queue
        self.infoset0 = {"player_position": "firsthand",
                         'pos_name': 'firsthand',
                         "legal_action": self.get_legal_action(),
                         "player_points": self.get_point_mat(self.obs2state(self.player_0)),
                         "other_points": np.zeros((6, 7)),
                         "card_play_action_seq": np.zeros((2, 6 * 7 * 2))}

        self.infoset1 = {"player_position": "secondhand",
                         'pos_name': 'secondhand',
                         "legal_action": self.get_legal_action(),
                         "player_points": self.get_point_mat(self.obs2state(self.player_1)),
                         "other_points": np.zeros((6, 7)),
                         "card_play_action_seq": np.zeros((2, 6 * 7 * 2))
                         }
        logging.basicConfig(format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
                            level=logging.DEBUG,
                            filename='test.log',
                            filemode='a')
        self.logger = logging.getLogger('inference')

    def update_action_seq(self, action):
        self.action_history = np.concatenate((self.action_history, action.reshape(1, action.shape[0], action.shape[1])))
        self.action_history = self.action_history[1:, :, :]

    def get_acting_player_position(self):
        if self.acting_player_position == None:
            self.acting_player_position = 'firsthand'
        elif self.acting_player_position == 'firsthand':
            self.acting_player_position = 'secondhand'
        else:
            self.acting_player_position = 'firsthand'

    def obs2state(self, points):
        return points#[p - 1 for p in points]

    def get_legal_action(self, fist_hand_reset=False):
        # 动作一共37个,可用 6*7 的矩阵编码表示,并作为之后输入的向量形式
        # action_list 从小到大的排列方式
        # 先手的选手合法动作中要屏蔽掉结束动作
        action_list = [np.zeros((7, 7)) for i in range(37)]
        action_list[-1][0, 6] = 1

        for i in range(6):
            for j in range(6):
                if i * 6 + j >= len(action_list):
                    break
                action_list[i * 6 + j][:i + 1, j, ] = 1
        action_list = [mat[:6, :] for mat in action_list]
        if fist_hand_reset:
            return action_list[:-1]
        if self.last_action is None:
            return action_list

        else:
            for idx, array in enumerate(action_list):
                if np.array_equal(array, self.last_action):
                    return action_list[(idx + 1) % 37:]

    def get_point_mat(self, points):
        mat = np.zeros((6, 7))
        # print("points",points)
        for p in points:
            line = 0
            while mat[line, p - 1] == 1:
                line += 1
            mat[line, p] = 1
        return mat

    def reset(self):
        """重置两个玩家手里的骰子
        x_batch 是一批特征(不包括历史动作)。它还编码了动作特征。 [参考斗地主,视点数为手牌、输入所有动作组合、点亮所有合法的动作]
        pos_name 是当前环境正在做动作/下一个要做动作的玩家名,是变化的
        player_position 是玩家名、是固定的
        :return:
        """

        self.agent_names = ["firsthand", "secondhand"]  # AGENT_NAMES#['agent0','agent1']
        self.player_0 =[random.randint(0, 5) for i in range(3)]
        self.player_1 = [random.randint(0, 5) for i in range(3)]
        print("new_game firsthand dice {} second hand dice {}".format(self.player_0,self.player_1))
        self.acting_player_position = 'firsthand'
        self.obs = {0: self.player_0, 1: self.player_1}
        # print("player1 dice", self.player_1)
        legal_actions = [(i, j) for i in range(1, 7) for j in range(1, 7)] + [(1, 7)]
        leg_action_mat = np.zeros((6, 7))
        leg_action_mat[0, 5] = 1
        leg_action_mat[1, 5] = 1  # 两个六
        # 将action 并上 state
        # state_list = [(for i in len(sorted(self.player_0))]
        state_mat = np.zeros((1, 6, 7))
        for i in self.player_0:
            j = 0
            while state_mat[0, j, i] != 0:
                j += 1
            state_mat[0, j, i] = 1
        # todo 先手后手用一个模型还是不同模型???似乎应该用同一个模型?
        # history_acts = np.zeros((38, 6, 7))

        self.infoset0 = {"player_position": "firsthand",
                         'pos_name': 'firsthand',
                         "legal_action": self.get_legal_action(fist_hand_reset=True),
                         "player_points": self.get_point_mat(self.obs2state(self.player_0)),
                         "other_points": np.zeros((6, 7)),
                         "card_play_action_seq": np.zeros((2, 6 * 7 * 2))}

        self.infoset1 = {"player_position": "secondhand",
                         'pos_name': 'secondhand',
                         "legal_action": self.get_legal_action(),
                         "player_points": self.get_point_mat(self.obs2state(self.player_1)),
                         "other_points": np.zeros((6, 7)),
                         "card_play_action_seq": np.zeros((2, 6 * 7 * 2))
                         }
        return get_obs(self.infoset0)  #

    def cmd2action(self, cmd):

        return cmd

    def decide_the_winner(self, action0):
        # 进入这个函数说明acting_player_position喊了结束(不信),win_flag
        reward = {0: 0, 1: 0}
        done = False
        last_cmd = self.action_history[-2, :, :]
        indices = np.where(last_cmd == 1)
        # print('indices',indices)
        target_dice_num = len(indices[0])
        target_dice_point = int(indices[1][0])

        dice_set = self.player_0 + self.player_1
        replaced_dice_set = [target_dice_point if dice == 0 else dice for dice in dice_set]
        real_dice_count = replaced_dice_set.count(target_dice_point)
        # target_dice_count = last_cmd[1][0]
        real_less_than_boasting = target_dice_num <= real_dice_count  # 代表上家是否赢得比赛
        if self.acting_player_position == "firsthand":

            reward[0] = -1 if real_less_than_boasting else 1  # 1-win_flag
            reward[1] = 1 if real_less_than_boasting else -1  # int(win_flag)
        else:
            reward[0] = 1 if real_less_than_boasting else -1  # int(win_flag)
            reward[1] = -1 if real_less_than_boasting else 1  # 1-win_flag
        # print("重置 reward ",reward,replaced_dice_set)
        # print("reward", reward)
        return reward, True

    def step(self, cmd):
        """轮流输入两个各自的动作,并将其动作加入obs中
        Arg:
            cmd: 长度为2的元组,第一位表示玩家0或者1,第二位为长度为2的元组,表示对应玩家给出的动作,如玩家0,做出动作四个2 (0,(4,2)),
            定义'开'动作为(6,7)
        Return:
            obs: 长为2的字典,每个值是长度为2的元组,第一位为长度为6的列表,表示双方手中的骰子点数,当回合未结束时只可知道自己的三个,形式[1,2,3,0,0,0,],
            当做‘开’动作后即揭示双方的骰子如[1,2,3,1,2,3];第二位为双方的历史动作列表 [(0,(4,2),(1,(5,2)]
            输入的cmd的玩家为

            reward: 收到动作‘开’时,判断胜负,返回双方的奖励,胜1,负-1
        """
        action0 = self.cmd2action(cmd)
        self.last_action = action0
        print("{} {}说 {}个{}".format(self.env_id_ran, self.acting_player_position, np.where(action0 == 1)[0][-1] + 1,
                                      np.where(action0 == 1)[1][-1]), end=" ")
        print("legal_action {} {}".format(len(self.infoset0['legal_action']), len(self.infoset1['legal_action'])))
        self.logger.info("{} {}说 {}个{} legal_action {} {}".format(self.env_id_ran, self.acting_player_position,
                                                            np.where(action0 == 1)[0][-1] + 1,
                                                            np.where(action0 == 1)[1][-1] + 1,
                                                            len(self.infoset0['legal_action']),
                                                            len(self.infoset1['legal_action'])))
        # self.action_history.append(action0)
        self.update_action_seq(action0)
        reward = {0: 0, 1: 0}
        done = False
        if np.array_equal(cmd, self.end_action):
            # 是否进入结束状态
            # print("real dice", self.player_0, self.player_1)
            reward, done = self.decide_the_winner(action0)
            # dict_keys(['position', 'x_batch', 'z_batch', 'legal_actions', 'x_no_action', 'z'])

        if self.acting_player_position == 'firsthand':
            # 输出pos_name(下一step要做动作的玩家) 要和 其他信息 对应
            # self.infoset0['legal_action'] = self.get_legal_action()

            self.infoset1['card_play_action_seq'] = self.action_history
            self.infoset1['legal_action'] = self.get_legal_action()
            self.get_acting_player_position()
            self.infoset1['pos_name'] = self.acting_player_position
            obs = get_obs(self.infoset1)

            return obs, reward[0], done, {}
        else:

            self.infoset0['legal_action'] = self.get_legal_action()
            self.infoset0['card_play_action_seq'] = self.action_history
            self.get_acting_player_position()
            self.infoset0['pos_name'] = self.acting_player_position
            obs = get_obs(self.infoset0)

            return obs, reward[0], done, {}

        4.效果展示和评估

效果展示

损失函数

获得回报

 由于两方都是强化学习模型,且网络和更新参数都相同,训练过程中return接近于0较为合理,并且可见,在初期随机探索之后firsthand的return始终保持在大于0的水平,可见改游戏有一定的先手优势。

推演视频demo

关于我与robot扔骰子玩吹牛被灌了六杯酒这件事

评估

经四小时训练后的先手模型,与笔者对战十局后,笔者胜三局,模型胜七局。对局日志如下,从日志可见模型不仅仅评估每种类型状态出现概率,其动作也具有一定欺骗性。

new_game firsthand dice [2, 3, 5] second hand dice [5, 3, 5]
31 firsthand说 2个4 legal_action 36 37
2个5
31 secondhand说 2个5 legal_action 36 26
31 firsthand说 3个4 legal_action 25 26
3个5
31 secondhand说 3个5 legal_action 25 20
31 firsthand说 1个6 legal_action 19 20
new_game firsthand dice [1, 2, 3] second hand dice [3, 0, 0]
31 firsthand说 3个1 legal_action 36 37
4个1
31 secondhand说 4个1 legal_action 36 23
31 firsthand说 1个6 legal_action 17 23
new_game firsthand dice [0, 2, 3] second hand dice [0, 1, 0]
31 firsthand说 2个5 legal_action 36 37
3个1
31 secondhand说 3个1 legal_action 36 25
31 firsthand说 4个1 legal_action 23 25
5个1
31 secondhand说 5个1 legal_action 23 17
31 firsthand说 1个6 legal_action 11 17
new_game firsthand dice [4, 4, 3] second hand dice [4, 2, 1]
31 firsthand说 2个5 legal_action 36 37
开
31 secondhand说 1个6 legal_action 36 25
new_game firsthand dice [0, 2, 3] second hand dice [5, 0, 3]
31 firsthand说 2个5 legal_action 36 37
3个3
31 secondhand说 3个3 legal_action 36 25
31 firsthand说 4个3 legal_action 21 25
4个5
31 secondhand说 4个5 legal_action 21 15
31 firsthand说 1个6 legal_action 13 15
new_game firsthand dice [1, 3, 0] second hand dice [4, 3, 2]
31 firsthand说 2个5 legal_action 36 37
3个5
31 secondhand说 3个5 legal_action 36 25
31 firsthand说 1个6 legal_action 19 25
new_game firsthand dice [4, 4, 5] second hand dice [4, 5, 4]
31 firsthand说 2个5 legal_action 36 37
3个5
31 secondhand说 3个5 legal_action 36 25
31 firsthand说 1个6 legal_action 19 25
new_game firsthand dice [1, 2, 2] second hand dice [3, 1, 0]
31 firsthand说 3个1 legal_action 36 37
4个1
31 secondhand说 4个1 legal_action 36 23
31 firsthand说 5个1 legal_action 17 23
开
31 secondhand说 1个6 legal_action 17 11
new_game firsthand dice [3, 1, 5] second hand dice [5, 1, 4]
31 firsthand说 3个1 legal_action 36 37
3个2
31 secondhand说 3个2 legal_action 36 23
31 firsthand说 1个6 legal_action 22 23
new_game firsthand dice [5, 2, 2] second hand dice [3, 5, 4]
31 firsthand说 2个4 legal_action 36 37
2个5
31 secondhand说 2个5 legal_action 36 26
31 firsthand说 3个5 legal_action 25 26
4个5
31 secondhand说 4个5 legal_action 25 19
31 firsthand说 1个6 legal_action 13 19
new_game firsthand dice [4, 3, 3] second hand dice [1, 3, 5]
31 firsthand说 2个5 legal_action 36 37
3个2
31 secondhand说 3个2 legal_action 36 25
31 firsthand说 3个5 legal_action 22 25
开
31 secondhand说 1个6 legal_action 22 19

三 改进展望

        通过与真人对战发现,模型在最开始几局可以凭借欺骗获胜,但在10局之后,人类玩家能够逐渐掌握其动作策略的惯性,及时调整自身策略与之相对。如,玩家发现RL模型在有一个6时往往会叫2个6,真对这一惯性,玩家在确认自己无6的情况下调整策略为直接开牌,取得对局胜利。

        对此问题,一种可能的解决方案是,在每局结束时不清空state张量,而将最新state加在旧的张量的最后一维,使模型具有跨对局的记忆能力,以期其获得玩家策略调整时,自身策略随之调整的能力

四 参考文献

[1] Zha, Daochen et al. “DouZero: Mastering DouDizhu with Self-Play Deep Reinforcement Learning.” ICML (2021).

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1586307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS 开发-阻塞事件冒泡

介绍 本示例主要介绍在点击事件中&#xff0c;子组件enabled属性设置为false的时候&#xff0c;如何解决点击子组件模块区域会触发父组件的点击事件问题&#xff1b;以及触摸事件中当子组件触发触摸事件的时候&#xff0c;父组件如果设置触摸事件的话&#xff0c;如何解决父组…

应对人工智能在金融服务业的迅猛发展

今天分享的是人工智能专题系列深度研究报告&#xff1a;《人工智能专题&#xff1a;应对人工智能在金融服务业的迅猛发展》。 &#xff08;报告出品方&#xff1a;安永&#xff09; 评估人工智能对金融服务业的潜在影响 虽然大型银行和保险公司使用人工智能已有多年&#xf…

Rust语言入门第一篇-环境搭建

Rust语言入门第一篇 Rust官网 一&#xff0c;环境搭建 1、C开发环境配置 Rust 语言的底层是依赖于 C/C 编译器的。在安装 Rust 编译器时&#xff0c;通常会自动安装所需的 C/C 编译环境&#xff0c;以便 Rust 能够生成可执行文件或库。因此&#xff0c;在安装 Rust 之前&…

牛客NC413 两个升序数组的中位数【hard 数组,模拟 Java、Go、PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/b3b59248e61f499482eaba636305474b 思路 直接模拟2个数组有顺序放到一个数组中help中如果help长度为奇数&#xff0c;返回中间的数如果help长度为偶数&#xff0c;返回中间2个数的和除以2参考答案java import j…

【学习】移动端兼容性测试有什么方法及重要性

随着移动互联网的快速发展&#xff0c;移动应用程序已经成为人们日常生活中不可或缺的一部分。然而&#xff0c;由于各种移动设备的硬件和软件差异&#xff0c;移动应用程序的兼容性问题也越来越突出。因此&#xff0c;移动端兼容性测试成为了一个重要的环节&#xff0c;它可以…

jar包瘦身 移除无用的maven依赖

1.进入pom.xml所在目录&#xff0c;执行mvn dependency:analyze Unused declared dependencies found:下的单个包进行逐个移除编译&#xff0c;编译不报错就可以移除

3d怎么按路径制作模型---模大狮模型网

在3D建模中&#xff0c;按路径制作模型是一种常见的技术&#xff0c;特别适用于创建曲线、管道、绳索等线性形状的物体。虽然这项技术可能对初学者来说有些复杂&#xff0c;但通过一步步的指导和实践&#xff0c;你将能够掌握它。本文将详细介绍按路径制作模型的步骤&#xff0…

4月9号总结

java学习 一.steam流 1.介绍 Stream 是 Java 8 中引入的一种处理集合数据的新抽象。它提供了一种高效且便利的方式来处理集合中的元素&#xff0c;支持函数式编程的特性&#xff0c;使得集合操作变得更加简洁和灵活。 2.创建 List和Set可以直接调用接口的steam方法转换为流 …

vue2+codemirror实现指定行背景染色(二 代码染色)

在做覆盖率统计时,我们需要给指定代码行染色高亮,这里使用vue2+codemirror实现代码的渲染和染色。效果图如下: 1、安装 vue-codemirror // 指定安装4.x版本 // 目前最新版本6.x,仅支持Vue3.0 npm i vue-codemirror@4.x --save// codemirror 需要与 vue-codemirror 同时安装…

JavaScript_语法--变量

1.4 变量 变量&#xff1a;一小块存储数据的内存空间 Java语言是强类型语言&#xff0c;而JavaScript是弱类型的语言 强类型&#xff1a; 在开辟变量存储空间时&#xff0c;定义了空间将来存储的数据的数据类型。只能存储固定类型的数据 弱类型&#xff1a; 在开辟变量存储空间…

Ollama教程——兼容OpenAI API:高效利用兼容OpenAI的API进行AI项目开发

相关文章: Ollama教程——入门&#xff1a;开启本地大型语言模型开发之旅 Ollama教程——模型&#xff1a;如何将模型高效导入到ollama框架 Ollama教程——兼容OpenAI API&#xff1a;高效利用兼容OpenAI的API进行AI项目开发 Ollama教程——兼容OpenAI API&#xff1a;高效利用…

我为什么选择成为程序员?

前言&#xff1a; 我选择成为程序员不是兴趣所在&#xff0c;也不是为了职业发展&#xff0c;全是生活所迫&#xff01; 第一章&#xff1a;那年&#xff0c;我双手插兜&#xff0c;对外面的世界一无所知 时间回到2009年&#xff0c;时间过得真快啊&#xff0c;一下就是15年前…

基于小程序实现的校园失物招领系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

加固系统安全,防范ssh暴力破解之Fail2Ban

你是否还在担心你的服务器被攻击&#xff1f;你是否还在担心你的博客的安全&#xff1f;你是否还在担心你的隐私&#xff1f;别急fail2ban它来了&#xff0c;它可以解决你的一切问题。 Fail2Ban 是什么&#xff1f; 现在让我们一起来认识一下今天的主角 – Fail2Ban。简单说来…

多级菜单Mysql数据库表设计与创建

1.还是以Vue实现学院官网为例 文章地址&#xff1a;http://t.csdnimg.cn/jrJhE Vue 实现学院官网“菜单”当时是使用静态数据&#xff0c;也就是在页面上写死了的。 今天我们需要将“菜单”数据在数据库中进行维护&#xff0c;我们使用的是Mysql数据库 2.数据库的设计 我们的…

2023年通用人工智能AGI等级保护白皮书

今天分享的是人工智能专题系列深度研究报告&#xff1a;《人工智能专题&#xff1a;2023年通用人工智能AGI等级保护白皮书》。 通用人工智能发展现状 本章主要介绍通用人工智能的基本情况&#xff0c;包括其发展历史、现状以及组成架构等内容。本文还将通过从技术角度出发来分…

btSoftRigidDynamicsWorld 类是 Ammo.js 物理库中的一个类,表示一个动态世界,用于处理软体和刚体物体的物理模拟。

demo案例 btSoftRigidDynamicsWorld 类是 Ammo.js 物理库中的一个类&#xff0c;表示一个动态世界&#xff0c;用于处理软体和刚体物体的物理模拟。让我们按照输入参数、输出、属性和方法来详细解释其 API&#xff1a; 输入参数&#xff1a; dispatcher&#xff1a;这是一个…

多线程的学习

多线程编辑&#xff1a; 可以简单理解进程是一个软件 而线程就是一个软件中多个可以同时运行的功能 实现多线程的第一种方式&#xff1a;使用Thead类我们再自己创造一个类继承于这个类我们在对Thead方法进行重写&#xff0c;注意我们再重写的时候一定要加上Override这行 我犯下…

Linux下使用C语言实现高并发服务器

高并发服务器 这一个课程的笔记 相关文章 协议 Socket编程 高并发服务器实现 线程池 使用多进程并发服务器时要考虑以下几点&#xff1a; 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)系统内创建进程个数(与内存大小相关)进程创建过多是否降低整体…

《看漫画学C++》第12章 可大可小的“容器”——向量

在C编程的世界里&#xff0c;数组是一种基础且广泛使用的数据结构。然而&#xff0c;传统的静态数组在大小固定、管理不便等方面的局限性&#xff0c;常常让开发者感到束手束脚。幸运的是&#xff0c;C标准库中的vector类为我们提供了一种更加灵活、高效的动态数组解决方案。 …