当前位置: 首页 > news >正文

【Skynet 入门实战练习】游戏模块划分 | 基础功能模块 | timer 定时器模块 | logger 日志服务模块

文章目录

    • 游戏模块
    • 基础功能模块
      • 定时器模块
      • 日志模块
      • 通用模块

游戏模块

游戏从逻辑方面可以分为下面几个模块:

  • 注册和登录
  • 网络协议
  • 数据库
  • 玩法逻辑
  • 其他通用模块

除了逻辑划分,还有几个重要的工具类模块:

  • Excel 配置导表工具
  • GM 指令
  • 测试机器人
  • 服务器打包部署工具

本节先来实现几个通用的基础功能模块。

基础功能模块

定时器模块

在什么场景下,我们会要使用到定时器?

  • 每日任务的重置,比如游戏在每天的 0 点,需要定时进行刷新
  • 登录流程的超时机制,对于长时间未通过验证的连接,需要踢客户端下线,避免占用服务端资源
  • 活动结算,在定期活动结束后,需要给所有用户发放结算奖励

服务端和客户端都可以实现定时器逻辑,一般涉及到全服玩家的定时器,需要服务器来实现,针对个人玩家的定时器可以交给客户端来实现。


skynet 中,通过 skynet.timeout(time, func) 实现定时任务,skynet 基本的时间单位是 10ms,即会在 0.01s 后执行一次 func 函数。

参考:https://github.com/cloudwu/skynet/wiki/LuaAPI

skynet.timeout(ti, func) 让框架在 ti 个单位时间后,调用 func 这个函数。这不是一个阻塞 API ,当前 coroutine 会继续向下运行,而 func 将来会在新的 coroutine 中执行。

skynet 的定时器实现的非常高效,所以一般不用太担心性能问题。不过,如果你的服务想大量使用定时器的话,可以考虑一个更好的方法:即在一个service里,尽量只使用一个 skynet.timeout ,用它来触发自己的定时事件模块。这样可以减少大量从框架发送到服务的消息数量。毕竟一个服务在同一个单位时间能处理的外部消息数量是有限的。

由此,考虑自己实现一个用于定时触发事件的定时器模块,并且不需要这么高的精度,采用以秒为单位实现定时器。

定时器模块实现架构: skynet.timeout 实现循环定时器,每秒循环一次,查看并执行这一秒对应的回调函数。定时器 id 采用自增唯一映射每个定时回调函数。注册回调函数时,会计算将要执行的秒数,存入对应的回调函数表。


基础变量以及模块初始化

local _M = {} local is_init = false   -- 标记模块是否初始化
local timer_inc_id = 1  -- 定时器的自增 ID
local cur_frame = 0     -- 当前帧,一帧对应一秒
local cur_timestamp = 0 -- 当前时间戳,运行到的秒数
local timer_size = 0    -- 定时器数量
local frame_size = 0    -- 帧数量local timer2frame = {}  -- 定时器ID 映射 帧
local frame2cbs = {}    -- 帧 映射 多个回调任务
--[[frame: {timers: {timerid: { sec, cb, args, is_repeat },timerid: { sec, cb, args, is_repeat }}, size: 1}
]]if not is_init then is_init = true -- 初始化定时器模块skynet.timeout(100, main_loop)
end return _M 
  • is_init:用于标记模块是否初始化,即生成一次循环定时器,定时每秒执行 main_loop 函数
  • timer_inc_id:定时器的唯一标识 ID,每个定时器创建,都会自增
  • cur_frame:记录当前循环是对应哪一帧,随着循环自增
  • cur_timestamp:当前循环时间戳
  • timer_sizeframe_size:维护的定时器数量和帧数量
  • timer2frame:定时器 ID 对应的帧
  • frame2cbs:帧对应的回调函数表

回调函数表的结构 frame2cbs

frame: {timers: {timerid: { sec, cb, args, is_repeat },timerid: { sec, cb, args, is_repeat }}, size: 1
}

每帧对应回调函数表,有 timerssize 两个字段,size 维护当前回调函数个数,timers 则是实际的回调函数表,以定时器 ID 映射对应的回调函数。

每个回调函数都存储 seccbargsis_repeat 四个字段,表示 sec 秒后执行 cb 函数,携带 args 参数, is_repeat 表示是否是一个循环任务。


下面看每帧执行的函数 main_loop

local function now() return skynet.time() // 1 -- 截断小数:.0
end     
-- 逐帧执行
local function main_loop()skynet.timeout(100, main_loop)cur_timestamp = now()cur_frame = cur_frame + 1-- 当前没有定时器任务if timer_size <= 0 then return end -- 当前帧对应的回调任务local cbs = frame2cbs[cur_frame]if not cbs then return end -- 当前帧的回调任务数量为0if cbs.size <= 0 then frame2cbs[cur_frame] = nil frame_size = frame_size - 1 -- 该帧执行完毕return end -- task: {sec, cb, args, is_repeat}for timerid, task in pairs(cbs.timers) do local f = task[2] local args = task[3]local ok, err = xpcall(f, traceback, unpack(args, 1, args.n))if not ok then logger.error("timer", "crontab is run in error:", err)end del_timer(timerid) -- 执行成功与否都需要删掉当前这个定时器local is_repeat = task[4]if is_repeat then local sec = task[1]init_timer(timerid, sec, f, args, is_repeat)end end -- 当前这一帧所有任务执行完,并且这一帧没有删(双重保障(del_timer)),删掉当前帧if frame2cbs[cur_frame] then frame2cbs[cur_frame] = nil frame_size = frame_size - 1end 
end 

这里在入口处,我们就立即需要执行 skynet.timeout(100, main_loop),实现循环定时,并且没有多余其他操作,保证一下秒定时的准确。

skynet.time():当前 UTC 时间(单位是秒, 精度是 ms)

主要逻辑:判断当前帧是否有任务,有则执行 frame2cbs[cur_frame].timers 回调函数表中的回调函数,执行完后进行删除和判断该回调是否是循环定时任务,是则重新创建该回调的新定时器。


再来看定时器的创建和删除逻辑

init_timer

local function init_timer(id, sec, f, args, is_repeat)-- 第一步:定时器 id 映射 帧local offset_frame = sec -- sec 帧后开始当前任务 -- 矫正帧数if now() > cur_timestamp then offset_frame = offset_frame + 1end -- 实际计算执行帧local fix_frame = cur_frame + offset_frame-- 第二步:该帧 映射 定时器任务local cbs = frame2cbs[fix_frame]if not cbs then -- 创新当前帧的任务集cbs = { timers = {}, size = 1 }frame2cbs[fix_frame] = cbs frame_size = frame_size + 1 else cbs.size = cbs.size + 1end cbs.timers[id] = {sec, f, args, is_repeat}timer2frame[id] = fix_frametimer_size = timer_size + 1if timer_size >= 500 then logger.warn("timer", "timer is too many!")end 
end 

创建定时器任务,对应需要修改 frame2cbstimer2frame 表。回调函数加入当前帧的回调表中,回调的定时器ID映射当前帧,一并维护一下定时器和帧的数量统计。

在函数的开始,我们进行了对帧的校正。保证回调任务在未来帧中执行,而不会在当前帧中继续添加任务。

del_timer

-- 删除定时器
local function del_timer(id) -- 获取定时器id 映射 帧local frame = timer2frame[id]if not frame then return end -- 获取该帧对应的任务local cbs = frame2cbs[frame]if not cbs or not cbs.timers then return end -- 如果这个帧中的定时器任务存在if cbs.timers[id] then cbs.timers[id] = nil -- 删除该定时器任务cbs.size = cbs.size - 1 -- 当前帧的任务数 -1end -- 当前删掉了这一帧的最后一个定时器任务if cbs.size == 0 then frame2cbs[frame] = nil -- 置空frame_size = frame_size - 1 -- 帧数 -1 end -- 当前定时器id对应的帧置空,且定时器数量 -1timer2frame[id] = nil timer_size = timer_size - 1
end 

删除定时器逻辑很好理解,传入定时器 ID,找到 ID 对应的帧,看该帧中是否存在这个任务,存在就删除并维护帧数和定时器数量。


接口实现:

-- 新增定时器 timer,sec 秒后执行函数 f
-- 返回定时器 ID
function _M.timeout(sec, f, ...)assert(sec > 0)timer_inc_id = timer_inc_id + 1init_timer(timer_inc_id, sec, f, pack(...), false)return timer_inc_id
end function _M.timeout_repeat(sec, f, ...) assert(sec > 0)timer_inc_id = timer_inc_id + 1init_timer(timer_inc_id, sec, f, pack(...), true)return timer_inc_id
end -- 取消定时器任务
function _M.cancel(id)del_timer(id)
end -- 检查定时器是否存在
function _M.exist(id)if timer2frame[id] then return true end return false 
end -- 获取定时器还有多久执行
function _M.get_remain(id)local frame = timer2frame[id] if frame then return frame - cur_frameend return -1
end 

完整代码:timer.lua


日志模块

日志系统一般分为 4 个等级:

  • DEBUG:调试用的日志,线上运行时屏蔽不输出
  • INFO:普通日志,线上运行时输出,流程的关键步骤都需要有 INFO 日志
  • WARN:数据异常,但不影响正常流程的时候输出
  • ERROR:数据异常,且需要人工处理的时候输出

日志服务模块配置如下:

-- log conf
logger = "log"
logservice = "snlua"
logpath = "log"
logtag = "game"
-- debug | info | warn | error 
log_level = "debug"

参考官方 wiki

logger 它决定了 skynet 内建的 skynet_error 这个 C API 将信息输出到什么文件中。如果 logger 配置为 nil ,将输出到标准输出。你可以配置一个文件名来将信息记录在特定文件中。

logservice 默认为 "logger" ,你可以配置为你定制的 log 服务(比如加上时间戳等更多信息)。可以参考 service_logger.c 来实现它。注:如果你希望用 lua 来编写这个服务,可以在这里填写 snlua ,然后在 logger 配置具体的 lua 服务的名字。在 examples 目录下,有 config.userlog 这个范例可供参考。

配置中,指定 loggerlog.lua 这个日志服务,logservicesnlua 表示这个日志服务是 lua 服务。其余的三个参数作为键值对存储在配置中,用于实现服务模块时取出使用。logpath 指定为日志存放的目录路径,logtag 指定为日志进程标识,log_level 可选四种日志级别。

这里先来看日志模块:lualib/logger.lua

local skynet = require "skynet"local loglevel = {debug = 0,info = 1,warn = 2,error = 3,
}local logger = {_level = nil,_fmt = "[%s] [%s] %s", -- [info] [label] msg_fmt2 = "[%s] [%s %s] %s", --[info] [label labeldata] msg
}local function init_log_level()if not logger._level thenlocal level = skynet.getenv "log_level"local default_level = loglevel.debuglocal valif not level or not loglevel[level] thenval = default_levelelseval = loglevel[level]endlogger._level = valend
endfunction logger.set_log_level(level)local val = loglevel.debugif level and loglevel[level] thenval = loglevel[level]endlogger._level = val
endlocal function formatmsg(loglevel, label, labeldata, args)local args_len = #argsif args_len > 0 thenfor k, v in pairs(args) dov = tostring(v)args[k] = vendargs = table.concat(args, " ")elseargs = ""endlocal msglocal fmt = logger._fmtif labeldata ~= nil thenfmt = logger._fmt2msg = string.format(fmt, loglevel, label, labeldata, args)elsemsg = string.format(fmt, loglevel, label, args)endreturn msg
end--[[
logger.debug("map", "user", 1024, "entered this map")
logger.debug2("map", 1, "user", 2048, "leaved this map")
]]
function logger.debug(label, ...)if logger._level <= loglevel.debug thenlocal args = {...}local msg = formatmsg("debug", label, nil, args)skynet.error(msg)end
end
function logger.debug2(label, labeldata, ...)if logger._level <= loglevel.debug thenlocal args = {...}local msg = formatmsg("debug", label, labeldata, args)skynet.error(msg)end
endfunction logger.info(label, ...)if logger._level <= loglevel.info thenlocal args = {...}local msg = formatmsg("info", label, nil, args)skynet.error(msg)end
end
function logger.info2(label, labeldata, ...)if logger._level <= loglevel.info thenlocal args = {...}local msg = formatmsg("info", label, labeldata, args)skynet.error(msg)end
endfunction logger.warn(label, ...)if logger._level <= loglevel.warn thenlocal args = {...}local msg = formatmsg("warn", label, nil, args)skynet.error(msg)end
end
function logger.warn2(label, labeldata, ...)if logger._level <= loglevel.warn thenlocal args = {...}local msg = formatmsg("warn", label, labeldata, args)skynet.error(msg)end
endfunction logger.error(label, ...)if logger._level <= loglevel.error thenlocal args = {...}local msg = formatmsg("error", label, nil, args, debug.traceback())skynet.error(msg)end
end
function logger.error2(label, labeldata, ...)if logger._level <= loglevel.error thenlocal args = {...}local msg = formatmsg("error", label, labeldata, args, debug.traceback())skynet.error(msg)end
endskynet.init(init_log_level)return logger

这个日志模块主要暴露的四个接口分别对应四个日志等级,并且只有当前日志等级 log_level 低于当前 API 对应的等级才可以输出。如果程序测试阶段,那么指定 debug 级,就会获得所有日志。如果程序上线指定 error 级,那么只会关注到最高级别的错误日志。

error 等级日志额外输出了调用堆栈,方便查看错误问题所在的位置。

skynet.init:若服务尚未初始化完成,则注册一个函数等服务初始化阶段再执行;若服务已经初始化完成,则立刻运行该函数。

下面再来看一下日志服务代码:service/log.lua

local skynet = require "skynet"
require "skynet.manager"
local time = require "utils.time"-- 日志目录
local logpath = skynet.getenv("logpath") or "log"
-- 日志文件名
local logtag  = skynet.getenv("logtag") or "game"
local logfilename = string.format("%s/%s.log", logpath, logtag)
local logfile = io.open(logfilename, "a+")-- 写文件
local function write_log(file, str) file:write(str, "\n")file:flush()print(str)
end -- 切割日志文件,重新打开日志
local function reopen_log() -- 下一天零点再次执行local future = time.get_next_zero() - time.get_current_sec()skynet.timeout(future * 100, reopen_log)if logfile then logfile:close() end local date_name = os.date("%Y%m%d%H%M%S", time.get_current_sec())local newname = string.format("%s/%s-%s.log", logpath, logtag, date_name)os.rename(logfilename, newname) -- logfilename文件内容剪切到newname文件logfile = io.open(logfilename, "a+") -- 重新持有logfilename文件
end -- 注册日志服务处理函数
skynet.register_protocol {name = "text", id = skynet.PTYPE_TEXT, unpack = skynet.tostring, dispatch = function(_, source, str)local now = time.get_current_time()str = string.format("[%08x][%s] %s", source, now, str)write_log(logfile, str)end 
}-- 捕捉sighup信号(kill -l) 执行安全关服逻辑
skynet.register_protocol {name = "SYSTEM", id = skynet.PTYPE_SYSTEM, unpack = function(...) return ... end,dispatch = function()-- 执行必要服务的安全退出操作skynet.sleep(100)skynet.abort()end 
}local CMD = {} skynet.start(function()skynet.register(".log")skynet.dispatch("lua", function(_, _, cmd, ...)local f = CMD[cmd]if f then skynet.ret(skynet.pack(f(...)))else skynet.error(string.format("invalid command: [%s]", cmd))endend)local ok, msg = pcall(reopen_log)if not ok then print(msg)end 
end)

日志服务已经在配置中指定,logger = "log"、logservice = "snlua",不需要自行启动这个日志服务。且项目中所有 skynet.error API 输出的内容都被定向到了日志文件中,而不是输出在控制台。便于调试,write_log 写日志函数,最后调用了 print 打印日志到了终端。

通过注册 skynet.PTYPE_TEXT 文本类型消息,那么项目中的 skynet.error 输出的日志都会经过本日志服务进行分发处理,由此在分发函数 dispatch = function(_, source, str) end 中处理日志消息,对所有的日志消息进行格式化的美观输出。

日志服务工作原理可以参考文章:https://www.jianshu.com/p/351ac2cfd98c/ ,本系列 skynet 偏原理性的东西不做深入讲解。

在这里插入图片描述

日志服务如果不做切割,全部放在一个文件中会导致日志文件日益增大,这里实现 reopen_log 函数,通过 skynet.timeout 定时每天零点对日志进行切割,包括在服务重启时,也会对上次的日志文件 game.log 进行分割处理。

在这里插入图片描述

日志服务还注册了一种消息类型,skynet.PTYPE_SYSTEM,用来接收 kill -1 命令的信号,触发保存数据的逻辑,待后续实现了缓存模块在完善。


通用模块

同样在本章节,继续实现几个通用模块,细心的小伙伴应该注意到了,在实现日志模块、日志服务时,都有导入 utils.time 这个处理时间的一个模块。

下图是目前的模块 lualib 文件夹的结构:

在这里插入图片描述

  • time.lua
local skynet = require "skynet"local _M = {}-- 一秒只转一次时间戳
local last_sec 
local current_str  -- 获取当前时间戳
function _M.get_current_sec()return math.floor(skynet.time())
end-- 获取下一天零点的时间戳
function _M.get_next_zero(cur_time, zero_point)zero_point = zero_point or 0 cur_time = cur_time or _M.get_current_sec() local t = os.date("*t", cur_time) if t.hour >= zero_point then t = os.date("*t", cur_time + 24 * 3600) end local zero_date = {year = t.year, month = t.month, day = t.day, hour = zero_point,min = 0, sec = 0,}return os.time(zero_date)
end -- 获取当前可视化时间
function _M.get_current_time() local cur = _M.get_current_sec()if last_sec ~= cur then current_str = os.date("%Y-%m-%d %H:%M:%S", cur)last_sec = cur endreturn current_str
endreturn _M 

目前实现了三个接口:

  • get_current_sec:获取当前时间戳
  • get_current_time:获取当前可视化时间
  • get_next_zero:获取下一天零点时间戳,可以自定义项目的刷新时间 zero_point

其中有一个小优化是 last_sec,current_str 设置上一秒时间戳,与当前可视化时间变量,保证一秒只会转换一次。

os.time、os.date 的使用参考 lua 手册


  • table.lua
local string = require "string"local _M = {}function _M.dump(t) local print_r_cache = {}local function sub_print_table(t, indent)if (print_r_cache[tostring(t)]) thenprint(indent .. "*" .. tostring(t))elseprint_r_cache[tostring(t)] = trueif (type(t) == "table") thenfor pos, val in pairs(t) doif (type(val) == "table") thenprint(indent .. "[" .. pos .. "] => " .. tostring(t) .. " {")sub_print_table(val, indent .. string.rep(" ", string.len(pos) + 8))print(indent .. string.rep(" ", string.len(pos) + 6) .. "}")elseif (type(val) == "string") thenprint(indent .. "[" .. pos .. '] => "' .. val .. '"')elseprint(indent .. "[" .. pos .. "] => " .. tostring(val))endendelseprint(indent .. tostring(t))endendendif (type(t) == "table") thenprint(tostring(t) .. " {")sub_print_table(t, "  ")print("}")elsesub_print_table(t, "  ")endprint()
endreturn _M 
  • string.lua
local string = require "string"local _M = {}function _M.split(str, sep) local arr = {}local i = 1for s in string.gmatch(str, "([^" .. sep .. "]+)") doarr[i] = si = i + 1endreturn arr
endreturn _M 

目前 table 模块仅实现了 dump 接口,对表的美化输出, string 模块仅实现了对字符串的分割转表,有需求在自定义添加更多的功能。

相关文章:

【Skynet 入门实战练习】游戏模块划分 | 基础功能模块 | timer 定时器模块 | logger 日志服务模块

文章目录 游戏模块基础功能模块定时器模块日志模块通用模块 游戏模块 游戏从逻辑方面可以分为下面几个模块&#xff1a; 注册和登录网络协议数据库玩法逻辑其他通用模块 除了逻辑划分&#xff0c;还有几个重要的工具类模块&#xff1a; Excel 配置导表工具GM 指令测试机器人…...

python内置模块binascii,二进制数据和ASCII字符串之间进行转换

一、简介 binascii是Python标准库中的一个模块&#xff0c;提供了在二进制数据和ASCII字符串之间进行转换的功能。它包含了一些用于处理二进制数据的函数&#xff0c;可以进行二进制数据的编码、解码和转换。 二、方法 binascii.unhexlify(hexstr)&#xff1a;将十六进制表示…...

如何开启MySQL的慢查询日志

说明&#xff1a;如果需要查看某一条SQL查询速度慢&#xff0c;并对慢的SQL进行优化&#xff0c;那么开启MySQL慢查询日志是一定要做的事情&#xff0c;本文介绍如何开启MySQL的慢查询日志&#xff1b; 查看MySQL慢查询是否开启 首先&#xff0c;输入下面的命令&#xff0c;查…...

Spine的BoundingBoxAttachment碰撞检测

引擎版本 —— cocos creator 2.3.4 游戏代码&#xff1a; //优先初始化的时候&#xff0c;获取到cc.PhysicsPolygonColliderthis._poly this.dragonFooAni.node.getComponent(cc.PhysicsPolygonCollider);//下面的修改顶点位置的方法可以在update里面去执行//获取骨骼动画上…...

Proteus下仿真AT89C51报“串行口通信失败,请检查电平适配是否正确。”解决办法

在Proteus下进行AT89C51串行口仿真时&#xff0c;如果遇到“串行口通信失败&#xff0c;请检查电平适配是否正确”的错误提示&#xff0c;以下是一些解决办法&#xff1a; 1. 了解AT89C51和外部设备的电平要求&#xff1a; 首先&#xff0c;了解AT89C51和外部设备之间的电平…...

微信小程序制作

如果你也想搭建一个小程序&#xff0c;但不知道如何入手&#xff0c;那么今天我就教你如何使用第三方制作平台&#xff0c;在短短三十分钟内搭建一个小程序。 一、登录小程序制作平台 首先&#xff0c;登录到小程序制作平台的官方网站或应用程序&#xff0c;进入后台管理系统。…...

快速在WIN11中本地部署chatGLM3

具体请看智谱仓库github&#xff1a;GitHub - THUDM/ChatGLM3: ChatGLM3 series: Open Bilingual Chat LLMs | 开源双语对话语言模型 或者Huggingface:https://huggingface.co/THUDM/chatglm3-6b 1. 利用Anaconda建立一个虚拟环境&#xff1a; conda create -n chatglm3 pyt…...

土地利用数据技术服务

一、背景介绍 土地是人类赖以生存与发展的重要资源和物质保障&#xff0c;在“人口&#xff0d;资源&#xff0d;环境&#xff0d;发展&#xff08;PRED&#xff09;”复合系统 中&#xff0c;土地资源处于基础地位。随着现代社会人口的不断增长以及工业化、城市化进程的加速&a…...

qml动画过渡Transition

文章目录 基本概念使用 `Transition`示例动画过渡高级用法示例:复杂动画过渡解释进阶用法在 QML 中,Transition 元素用于定义状态之间过渡时的动画。这是 QML 强大的状态机制的一部分,允许开发者创建平滑且吸引人的用户界面交互。使用 Transition,您可以定义当元素从一个状…...

Django(九、cookie与session)

文章目录 一、cookie与session的介绍HTTP四大特性 cookiesession Django操作cookie三板斧基于cookie的登录功能set_cookie 设置cookie 清空cookie设置cookie参数Django操作session设置session获取session清空sessionsession相关的参数设置过期时间 CBV添加装饰器 一、cookie与s…...

web前端之若依框架图标对照表、node获取文件夹中的文件名,并通过数组返回文件名、在html文件中引入.svg文件、require、icon

MENU 前言效果图htmlJavaScripstylenode获取文件夹中的文件名 前言 需要把若依原有的icon的svg文件拿到哦&#xff01; 注意看生成svg的路径。 效果图 html <div id"idSvg" class"svg_box"></div>JavaScrip let listSvg [404, bug, build, …...

使用 goland 开发 golang 项目环境配置

方式1&#xff1a;使用 GOPATH 和 GOROOT 在 goland 中打开&#xff1a;Settings - Go&#xff0c;会看到 GOROOT、GOPATH&#xff0c;其相关解释与配置如下&#xff1a; GOROOT&#xff1a;对应 go 的安装路径&#xff0c;例如&#xff1a;D:\go\binGOPATH&#xff1a;是我们…...

Linux宝塔面板搭建Discuz论坛, 并内网穿透实现公网访问

Linux宝塔面板搭建Discuz论坛&#xff0c; 并内网穿透实现公网访问 文章目录 Linux宝塔面板搭建Discuz论坛&#xff0c; 并内网穿透实现公网访问前言1.安装基础环境2.一键部署Discuz3.安装cpolar工具4.配置域名访问Discuz5.固定域名公网地址6.配置Discuz论坛 &#x1f4f7; 江池…...

[git] 忽略已经提交的文件或文件夹

文件已经被Git跟踪 如果某个文件已经被Git跟踪过&#xff08;即已经添加到版本控制中&#xff09;&#xff0c;.gitignore文件对该文件将不起作用。您需要使用以下命令将该文件从Git中移除&#xff1a; git rm --cached <文件> 支持文件夹 -r <文件夹>...

大模型增量预训练参数说明

在增量预训练过程中通常需要设置三类或四类参数,模型参数,数据参数,训练参数,额外参数。 下面分别针对这四种参数进行说明。 欢迎关注公众号 模型参数 model_type模型类型,例如bloom,llama,baichuan,qwen等。 model_name_or_path模型名称或者路径。 tokenizer_name_or…...

成为AI产品经理——模型评估概述

目录 一、模型宣讲和评估的原因 二、模型宣讲 三、模型评估 1. 重要特征 ① 特征来源 ②特征意义 2.选择测试样本 3.模型性能和稳定性 一、模型宣讲和评估的原因 刘海丰老师提到他们在做一个金融AI产品未注重模型指标&#xff0c;过于注重业务指标&#xff0c;导致产生…...

内存屏障与JVM指令

内存屏障是一种同步原语&#xff0c;用于确保在并发程序中&#xff0c;当一个线程对内存中的数据进行修改后&#xff0c;其他线程可以及时地获取到最新的数据。 内存屏障可以确保指令的执行具有原子性、可见性和顺序性。在JVM中&#xff0c;内存屏障通常通过插入一段特殊的指令…...

深入理解JVM 类加载机制

深入理解JVM 类加载机制 虚拟机如何加载Class文件&#xff1f; Class文件中的信息进入到虚拟机后会发生什么变化&#xff1f; 类加载机制就是Java虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机…...

SpringCloud微服务 【实用篇】| Eureka注册中心、Ribbon负载均衡

目录 一&#xff1a;Eureka注册中心 1. Eureka原理 2. 动手实践 ①搭建EurekaServer ②服务注册 ③服务发现 二&#xff1a;Ribbon负载均衡 1. 负载均衡原理 2. 负载均衡策略 3. 懒加载 tips&#xff1a;前些天突然发现了一个巨牛的人工智能学习网站&#xff0c;通俗…...

SpringSecurity+JWT权限认证

SpringSecurity默认的是采用Session来判断请求的用户是否登录的&#xff0c;但是不方便分布式的扩展 虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态&#xff0c;不过现在分布式的还是无状态的Jwt比较主流 一、创建SpringBoot的项目 spring-boot-starte…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...