GO goroutine状态流转

news2025/1/15 19:45:34

在这里插入图片描述

Gidle -> Grunnable

newproc获取新的goroutine,并放置到P运行队列中

这也是go关键字之后实际编译调用的方法

func newproc(fn *funcval) {
  // 获取当前正在运行中的goroutine
	gp := getg()
  // 获取调用者的程序计数器地址,用于调试和跟踪
	pc := getcallerpc()
	systemstack(func() {
    // 创建一个新的 goroutine 并返回新创建的 goroutine 结构 newg
		newg := newproc1(fn, gp, pc)

    // 获取当前P,并将新创建的goroutine放到P的运行队列中
		pp := getg().m.p.ptr()
		runqput(pp, newg, true)

    // 如果主 goroutine 已经启动(mainStarted 为 true),则调用 wakep() 唤醒或启动一个处理器以执行运行队列中的 goroutine
		if mainStarted {
			wakep()
		}
	})
}

newproc1用于获取新的goroutine

// Create a new g in state _Grunnable, starting at fn. callerpc is the
// address of the go statement that created this. The caller is responsible
// for adding the new g to the scheduler.
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
	if fn == nil {
		fatal("go of nil func value")
	}

	mp := acquirem() // disable preemption because we hold M and P in local vars.
	pp := mp.p.ptr()
  // 首先从P的gfree获取回收的g,如果没有,那么再从全局调度器sched中gfree窃取给P,再不然就只能调用malg()新建g
	newg := gfget(pp)
	if newg == nil {
		newg = malg(stackMin)
		casgstatus(newg, _Gidle, _Gdead)
		allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
	}
	if newg.stack.hi == 0 {
		throw("newproc1: newg missing stack")
	}

	if readgstatus(newg) != _Gdead {
		throw("newproc1: new g is not Gdead")
	}

  // 计算和分配堆栈指针
	totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // extra space in case of reads slightly beyond frame
	totalSize = alignUp(totalSize, sys.StackAlign)
	sp := newg.stack.hi - totalSize
	if usesLR {
		// caller's LR
		*(*uintptr)(unsafe.Pointer(sp)) = 0
		prepGoExitFrame(sp)
	}
	if GOARCH == "arm64" {
		// caller's FP
		*(*uintptr)(unsafe.Pointer(sp - goarch.PtrSize)) = 0
	}

  // 初始化调用帧
	memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
	newg.sched.sp = sp
	newg.stktopsp = sp
	newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
	newg.sched.g = guintptr(unsafe.Pointer(newg))
	gostartcallfn(&newg.sched, fn)
  
  // 设置新G元数据
	newg.parentGoid = callergp.goid
	newg.gopc = callerpc
	newg.ancestors = saveAncestors(callergp)
	newg.startpc = fn.fn
	if isSystemGoroutine(newg, false) {
		sched.ngsys.Add(1)
	} else {
		// Only user goroutines inherit pprof labels.
		if mp.curg != nil {
			newg.labels = mp.curg.labels
		}
		if goroutineProfile.active {
			// A concurrent goroutine profile is running. It should include
			// exactly the set of goroutines that were alive when the goroutine
			// profiler first stopped the world. That does not include newg, so
			// mark it as not needing a profile before transitioning it from
			// _Gdead.
			newg.goroutineProfiled.Store(goroutineProfileSatisfied)
		}
	}
	// Track initial transition?
	newg.trackingSeq = uint8(cheaprand())
	if newg.trackingSeq%gTrackingPeriod == 0 {
		newg.tracking = true
	}
	gcController.addScannableStack(pp, int64(newg.stack.hi-newg.stack.lo))

	// Get a goid and switch to runnable. Make all this atomic to the tracer.
  // 分配gid,并更新状态为_Grunnable
	trace := traceAcquire()
	casgstatus(newg, _Gdead, _Grunnable)
	if pp.goidcache == pp.goidcacheend {
		// Sched.goidgen is the last allocated id,
		// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
		// At startup sched.goidgen=0, so main goroutine receives goid=1.
		pp.goidcache = sched.goidgen.Add(_GoidCacheBatch)
		pp.goidcache -= _GoidCacheBatch - 1
		pp.goidcacheend = pp.goidcache + _GoidCacheBatch
	}
	newg.goid = pp.goidcache
	pp.goidcache++
	newg.trace.reset()
	if trace.ok() {
		trace.GoCreate(newg, newg.startpc)
		traceRelease(trace)
	}

	// Set up race context.
	if raceenabled {
		newg.racectx = racegostart(callerpc)
		newg.raceignore = 0
		if newg.labels != nil {
			// See note in proflabel.go on labelSync's role in synchronizing
			// with the reads in the signal handler.
			racereleasemergeg(newg, unsafe.Pointer(&labelSync))
		}
	}
	releasem(mp)

	return newg
}
malg 创建新goroutine

主要就是分配了goroutine栈空间

// Allocate a new g, with a stack big enough for stacksize bytes.
func malg(stacksize int32) *g {
	newg := new(g)
	if stacksize >= 0 {
		stacksize = round2(stackSystem + stacksize)
		systemstack(func() {
			newg.stack = stackalloc(uint32(stacksize))
		})
		newg.stackguard0 = newg.stack.lo + stackGuard
		newg.stackguard1 = ^uintptr(0)
		// Clear the bottom word of the stack. We record g
		// there on gsignal stack during VDSO on ARM and ARM64.
		*(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
	}
	return newg
}

runqput g置P中

先尝试放高优先的runnext槽中,然后放P本地队列,实在不行就放全局队列

// runqput tries to put g on the local runnable queue.
// If next is false, runqput adds g to the tail of the runnable queue.
// If next is true, runqput puts g in the pp.runnext slot.
// If the run queue is full, runnext puts g on the global queue.
// Executed only by the owner P.
func runqput(pp *p, gp *g, next bool) {
	if randomizeScheduler && next && randn(2) == 0 {
		next = false
	}

  // 如果next为真,则尝试将gp发到pp.runnext中,如果有正在操作的,再进行尝试
	if next {
	retryNext:
		oldnext := pp.runnext
		if !pp.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
			goto retryNext
		}
		if oldnext == 0 {
			return
		}
		// Kick the old runnext out to the regular run queue.
    // 如果已经有goroutine,则踢出并放入常规运行队列
		gp = oldnext.ptr()
	}

  // 加入常规运行队列
retry:
  // 加载运行队列的头指针和尾指针
	h := atomic.LoadAcq(&pp.runqhead) // load-acquire, synchronize with consumers
	t := pp.runqtail
  
  // 如果队列未满,则将gp直接放入队列中
	if t-h < uint32(len(pp.runq)) {
		pp.runq[t%uint32(len(pp.runq))].set(gp)
		atomic.StoreRel(&pp.runqtail, t+1) // store-release, makes the item available for consumption
		return
	}
  
  // 如果队列已满,则尝试慢策略
	if runqputslow(pp, gp, h, t) {
		return
	}
	// the queue is not full, now the put above must succeed
	goto retry
}

runqputslow将一次性将本地队列中的多个g放入到全局队列中

// Put g and a batch of work from local runnable queue on global queue.
// Executed only by the owner P.
func runqputslow(pp *p, gp *g, h, t uint32) bool {
	var batch [len(pp.runq)/2 + 1]*g

	// First, grab a batch from local queue.
  // 先从本地队列中抓取一批g
	n := t - h
	n = n / 2
	if n != uint32(len(pp.runq)/2) {
		throw("runqputslow: queue is not full")
	}
	for i := uint32(0); i < n; i++ {
		batch[i] = pp.runq[(h+i)%uint32(len(pp.runq))].ptr()
	}
  // 如果cas失败,说明已经有其它工作线程从_p_的本地运行队列偷走了一些goroutine,所以直接返回
	if !atomic.CasRel(&pp.runqhead, h, h+n) { // cas-release, commits consume
		return false
	}
	batch[n] = gp

	if randomizeScheduler {
		for i := uint32(1); i <= n; i++ {
			j := cheaprandn(i + 1)
			batch[i], batch[j] = batch[j], batch[i]
		}
	}

  // 将需要放入全局运行队列的g连起来,减少后面对全局链表的锁住时间,从而降低锁冲突
	// Link the goroutines.
	for i := uint32(0); i < n; i++ {
		batch[i].schedlink.set(batch[i+1])
	}
	var q gQueue
	q.head.set(batch[0])
	q.tail.set(batch[n])

	// Now put the batch on global queue.
  // 将链表放入全局队列中
	lock(&sched.lock)
	globrunqputbatch(&q, int32(n+1))
	unlock(&sched.lock)
	return true
}

Grunnable -> Gruning

将指定的goroutine绑定到当前M上,其状态设置为运行,然后运行实际代码

func execute(gp *g, inheritTime bool) {
  // 获取当前正在运行的M
	mp := getg().m

  // 如果goroutine是活跃的,那么尝试记录当前goroutine的堆栈信息
	if goroutineProfile.active {
		tryRecordGoroutineProfile(gp, osyield)
	}

  // goroutine与M相互绑定,并更新goroutine为运行态
	mp.curg = gp
	gp.m = mp
	casgstatus(gp, _Grunnable, _Grunning)
  
  // 初始化goroutine其他信息
	gp.waitsince = 0
	gp.preempt = false
	gp.stackguard0 = gp.stack.lo + stackGuard
	if !inheritTime {
		mp.p.ptr().schedtick++
	}

	// Check whether the profiler needs to be turned on or off.
	hz := sched.profilehz
	if mp.profilehz != hz {
		setThreadCPUProfiler(hz)
	}

	trace := traceAcquire()
	if trace.ok() {
		// GoSysExit has to happen when we have a P, but before GoStart.
		// So we emit it here.
		if !goexperiment.ExecTracer2 && gp.syscallsp != 0 {
			trace.GoSysExit(true)
		}
		trace.GoStart()
		traceRelease(trace)
	}

  // 切换到goroutine调度上下文,实际执行goroutine代码
  // 就是从g0切换到g栈空间,并执行g的用户代码
	gogo(&gp.sched)
}

Gruning -> Gdead

对普通M(不是M0)而言,执行完任务之后,会进行到goexit,并等待重新调度

// Finishes execution of the current goroutine.
func goexit1() {
	if raceenabled {
		racegoend()
	}
	trace := traceAcquire()
	if trace.ok() {
		trace.GoEnd()
		traceRelease(trace)
	}
	mcall(goexit0)
}

// goexit continuation on g0.
func goexit0(gp *g) {
  // 销毁
	gdestroy(gp)
  // 调度:查找一个可运行的goroutine并执行
	schedule()
}
gdestroy 销毁goroutine

销毁其实就是更新状态为Gdead,清除goroutine数据,重新放回P空闲池中

func gdestroy(gp *g) {
  // 获取当前M与P
	mp := getg().m
	pp := mp.p.ptr()

  // 更新goroutine状态为Gdead
	casgstatus(gp, _Grunning, _Gdead)
  
  // 更新GC控制器。减少可扫描的堆栈大小
	gcController.addScannableStack(pp, -int64(gp.stack.hi-gp.stack.lo))
  // 如果是系统goroutine,减少计数
	if isSystemGoroutine(gp, false) {
		sched.ngsys.Add(-1)
	}
  
  // 清除goroutine状态
	gp.m = nil
	locked := gp.lockedm != 0
	gp.lockedm = 0
	mp.lockedg = 0
	gp.preemptStop = false
	gp.paniconfault = false
	gp._defer = nil // should be true already but just in case.
	gp._panic = nil // non-nil for Goexit during panic. points at stack-allocated data.
	gp.writebuf = nil
	gp.waitreason = waitReasonZero
	gp.param = nil
	gp.labels = nil
	gp.timer = nil

	if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 {
		// Flush assist credit to the global pool. This gives
		// better information to pacing if the application is
		// rapidly creating an exiting goroutines.
		assistWorkPerByte := gcController.assistWorkPerByte.Load()
		scanCredit := int64(assistWorkPerByte * float64(gp.gcAssistBytes))
		gcController.bgScanCredit.Add(scanCredit)
		gp.gcAssistBytes = 0
	}

  // 释放当前M的goroutine
	dropg()

	if GOARCH == "wasm" { // no threads yet on wasm
		gfput(pp, gp)
		return
	}

	if mp.lockedInt != 0 {
		print("invalid m->lockedInt = ", mp.lockedInt, "\n")
		throw("internal lockOSThread error")
	}
  
  // 将 goroutine 放回空闲池
	gfput(pp, gp)
	if locked {
		// The goroutine may have locked this thread because
		// it put it in an unusual kernel state. Kill it
		// rather than returning it to the thread pool.

		// Return to mstart, which will release the P and exit
		// the thread.
		if GOOS != "plan9" { // See golang.org/issue/22227.
			gogo(&mp.g0.sched)
		} else {
			// Clear lockedExt on plan9 since we may end up re-using
			// this thread.
			mp.lockedExt = 0
		}
	}
}

Grunning -> Gwaiting

park_m主要负责将当前的 goroutine 暂停,切换其状态,并在必要时重新调度执行

// park continuation on g0.
func park_m(gp *g) {
	mp := getg().m

	trace := traceAcquire()

	// N.B. Not using casGToWaiting here because the waitreason is
	// set by park_m's caller.
  // 更改goroutine状态从Grunning到Gwaiting
	casgstatus(gp, _Grunning, _Gwaiting)
	if trace.ok() {
		trace.GoPark(mp.waitTraceBlockReason, mp.waitTraceSkip)
		traceRelease(trace)
	}

  // 将当前goroutine与M分离
	dropg()

  // 确保在某些条件下,安全地将 Goroutine 从等待状态移出,处理失败的解锁操作,并在必要时重新调度该 Goroutine
   // 调用解锁函数
	if fn := mp.waitunlockf; fn != nil {
		ok := fn(gp, mp.waitlock)
		mp.waitunlockf = nil
		mp.waitlock = nil
		if !ok {
			trace := traceAcquire()
			casgstatus(gp, _Gwaiting, _Grunnable)
			if trace.ok() {
				trace.GoUnpark(gp, 2)
				traceRelease(trace)
			}
			execute(gp, true) // Schedule it back, never returns.
		}
	}
  
  // 调度,切换到其他 goroutine 执行
	schedule()
}

Gwaiting -> Grunable

ready将指定的 goroutine 标记为可运行状态,并将其放入运行队列中

// Mark gp ready to run.
func ready(gp *g, traceskip int, next bool) {
  // 读取 gp 的当前状态
	status := readgstatus(gp)

  // 标记为可运行
	// Mark runnable.
	mp := acquirem() // disable preemption because it can be holding p in a local var
	if status&^_Gscan != _Gwaiting {
		dumpgstatus(gp)
		throw("bad g->status in ready")
	}

	// status is Gwaiting or Gscanwaiting, make Grunnable and put on runq
	trace := traceAcquire()
	casgstatus(gp, _Gwaiting, _Grunnable)
	if trace.ok() {
		trace.GoUnpark(gp, traceskip)
		traceRelease(trace)
	}
  
  // 将 gp 放入当前 P 的运行队列中。如果 next 为 true,表示将 gp 放在队列的前面,否则放在队列的后面
	runqput(mp.p.ptr(), gp, next)
  // 确保有一个 P 可以运行 gp
	wakep()
  // 重新启用当前M的抢占
	releasem(mp)
}

Grunning -> Grunnable

goschedImpl 函数用于将当前 goroutine 交出 CPU,使其重新排队等待调度执行。它的实现涉及状态检查、状态变更、跟踪信息处理、全局运行队列操作和重新调度

goschedImpl 函数执行的主要步骤包括:

  1. 获取并验证 gp 的当前状态。
  2. 将 gp 的状态修改为 _Grunnable,表示它现在是可运行状态。
  3. 解绑当前 M 和 gp。
  4. 获取调度器锁并将 gp 放入全局运行队列。
  5. 如果主 goroutine 已经启动,则唤醒一个 P 以确保运行队列中的 goroutine 被处理。
  6. 重新进入调度循环,选择下一个可运行的 goroutine 开始执行。
func goschedImpl(gp *g, preempted bool) {
	trace := traceAcquire()
	status := readgstatus(gp)
	if status&^_Gscan != _Grunning {
		dumpgstatus(gp)
		throw("bad g status")
	}
	casgstatus(gp, _Grunning, _Grunnable)
	if trace.ok() {
		if preempted {
			trace.GoPreempt()
		} else {
			trace.GoSched()
		}
		traceRelease(trace)
	}

	dropg()
	lock(&sched.lock)
	globrunqput(gp)
	unlock(&sched.lock)

	if mainStarted {
		wakep()
	}

	schedule()
}

大致总结下

go池是所有拥有goroutine的地方,包括P的runnext、P本地队列和全局队列

  • Gidle -> Grunnable: 初始化g,放入go池
  • Grunnable -> Grunning: 从go池取出,绑定M,执行实际代码
  • Grunning
    • -> Gdead: 解绑M,重置g,重新放入go池
    • -> Gwaiting: 解绑M,等待被唤醒
    • -> Grunnable: 解绑M,放入go全局队列
  • Gwaiting -> Grunable: 被唤醒后放入go池
  1. https://github.com/LeoYang90/Golang-Internal-Notes/blob/master/Go%20%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E2%80%94%E2%80%94%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E4%B8%8E%E5%88%9D%E5%A7%8B%E5%8C%96.md

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

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

相关文章

量化小白也能自动化挖掘出6万+因子

最近逛某乎&#xff0c;碰到了这个问题&#xff1a;如何看待量化交易WorldQuant世坤大赛北大牛人提交了6万因子&#xff1f; 我的第一直觉&#xff0c;这肯定不是纯手工挖出来的&#xff0c;6w个因子&#xff0c;一天挖一个&#xff0c;节假日都不休息的话&#xff0c;需要164年…

轻松入门Linux—CentOS,直接拿捏 —/— <6>vim集合

一、Vim操作详解 1、linux彩蛋 输入命令python会启动Python解释器&#xff0c;允许你输入和执行Python代码。然后&#xff0c;输入import this会导入this模块&#xff0c;它是Python的一种彩蛋&#xff08;Easter egg&#xff09;&#xff0c;然后得到下列结果 选中这段结果复…

Nacos安装教程(全网最靠谱,最简单~)

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 本文将详细介绍 Nacos 的安装及使用。 官方网址&#xff1a;Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方…

3.7.物体检测算法

物体检测算法 1.R-CNN ​ 首先使用启发式搜索算法来选择锚框&#xff0c;使用预训练模型对每个锚框抽取特征&#xff0c;训练一个SVM来对类别分类&#xff0c;最后训练一个线性回归模型来预测边缘框偏移。 ​ R-CNN比较早&#xff0c;所以使用的是SVM 1.1 兴趣区域(RoI)池化…

【Qwen2微调实战】LLaMA-Factory框架对Qwen2-7B模型的微调实践

系列篇章&#x1f4a5; No.文章1【Qwen部署实战】探索Qwen-7B-Chat&#xff1a;阿里云大型语言模型的对话实践2【Qwen2部署实战】Qwen2初体验&#xff1a;用Transformers打造智能聊天机器人3【Qwen2部署实战】探索Qwen2-7B&#xff1a;通过FastApi框架实现API的部署与调用4【Q…

【数据结构进阶】手撕红黑树

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; C || 数据结构 目录 &#x1f308;前言&#x1f525;红黑树的概念&#x1f525;手撕红黑树红黑树结点的定义红黑树主体需要实现的成员函数红黑树的插入findEmpty和Size拷贝构造析构函数和…

CANFD报文 位时间 理解

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

PCB设计——51单片机核心板布线以及原理图

首先是最小系统板&#xff0c;包括晶振电路&#xff0c;电源电路&#xff0c;复位电路 对应pcb板图

HTTP协议:网络通信的基石

一、引言 HTTP&#xff08;HyperText Transfer Protocol&#xff09;&#xff0c;即超文本传输协议&#xff0c;是当今互联网世界中最为重要的协议之一。它是客户端和服务器之间进行通信的规则和标准&#xff0c;使得我们能够在浏览器中浏览网页、下载文件、提交表单等各种操作…

AT32F403A/421 SVPWM驱动无刷电机开环速度测试

AT32F403A/421 SVPWM驱动无刷电机开环速度测试 &#x1f4cc;相关篇《HAL STM32F4 ARM DSP库跑SVPWM开环速度测试》 ✨本测试工程基于上面的运行例程移植而来。主要用来测试驱动无刷电机性能方面的差异。 &#x1f516;工程基于AT32_Work_Bench创建。 &#x1f530;AT32F403A和…

卷积神经网络随记

1.问题描述&#xff1a;一般而言&#xff0c;几个小滤波器卷积层的组合比一个大滤波器卷积层要好&#xff0c;比如层层堆叠了3个3x3的卷积层&#xff0c;中间含有非线性激活层&#xff0c;在这种排列下面&#xff0c;第一个卷积层中每个神经元对输入数据的感受野是3x3&#xff…

Verilog语言和C语言的本质不同点是什么?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c语言的资料从专业入门到高级教程」&#xff0c;点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 在c语言中&#xff0c;如果你…

7.Redis的Hash类型

Hash类型&#xff0c;也叫散列&#xff0c;其value是一个无序字典&#xff0c;类似于HashMap结构。 问题 String结构是将对象序列化为json字符串后存储&#xff0c;当需要修改对象某个字段是不是很方便。 key value…

【计算机遥感方向】SCI期刊推荐!水刊、顶刊齐聚在此,速投!

本期将为您带来五本计算机SCI 妥妥毕业神刊&#xff01; IEEE TRANSACTIONS ON GEOSCIENCE AND REMOTE SENSING International Journal of Applied Earth Observation and Geoinformation INTERNATIONAL JOURNAL OF REMOTE SENSING Geocarto International RADIO SCIEN…

蔚来智驾的大模型之路:自研芯片 + 世界模型 + 群体智能

作者 |德新 编辑 |王博 7月27日上周末&#xff0c;蔚来举办第二届NIO IN。 李斌说&#xff0c;2023年的第一届NIO IN像是一个大纲&#xff0c;第一次对外完整展示了蔚来布局的12大技术领域。 而这届&#xff0c;更像第一个交付的章节。它重点展示了5项阶段性的进展&#xff…

智能电池管理,soc、soh、comsol锂电池仿真

锂离子电池&#xff0c;作为能源转型与电动车市场崛起的基石&#xff0c;正迎来研发与应用的飞跃。面对繁杂设计参数与实验盲点&#xff0c;电池仿真技术&#xff0c;尤以COMSOL为代表的多物理场仿真&#xff0c;精准解析电池内部机理&#xff0c;从微观行为到宏观性能&#xf…

LoRA:大模型的轻量级高效微调方法

文章目录 1. 模型微调的两种方式2. LoRA 实现 LoRA是一种轻量化且效果非常突出的大模型微调方法&#xff0c;与使用Adam微调的GPT-3 175B相比&#xff0c;LoRA可以将可训练参数的数量减少10000倍&#xff0c;并将GPU内存需求减少3倍。 paper&#xff1a;LoRA: Low-Rank Adapta…

二维码门楼牌管理应用平台建设:流程优化与全面考量

文章目录 前言一、工作流程优化&#xff1a;移动端采集与实时更新二、数据完整性与准确性保障三、效率提升与成本节约四、扩展性与未来发展五、数据安全与隐私保护六、用户培训与技术支持 前言 随着智慧城市建设的不断深入&#xff0c;二维码门楼牌管理应用平台作为城市管理的…

电脑浏览器缓存怎么清除 Mac电脑如何清理浏览器缓存数据 macbookpro浏览器怎么清理

浏览器已经成为我们日常生活中不可或缺的工具。然而&#xff0c;随着时间的推移&#xff0c;浏览器缓存的积累可能会逐渐影响我们的上网体验&#xff0c;导致网页加载速度变慢、浏览器运行卡顿等问题。因此&#xff0c;定期清理浏览器缓存变得尤为重要。那么Mac怎么清除浏览器缓…

Springboot学习-day16

Springboot学习-day16 Springboot是spring家族中的一个全新框架&#xff0c;用来简化spring程序的创建和开发过程。在以往我们通过SpringMVCSpringMybatis框架进行开发的时候&#xff0c;我们需要配置web.xml&#xff0c;spring配置&#xff0c;mybatis配置&#xff0c;然后整…