从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载

本文为从零开始写 Docker 系列第六篇,实现类似 docker -v 的功能,通过挂载数据卷将容器中部分数据持久化到宿主机。
完整代码见:https://github.com/lixd/mydocker
欢迎 Star
推荐阅读以下文章对 docker 基本实现有一个大致认识:
- 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
- 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
- 基于 cgroups 的资源限制
- 初探 Linux Cgroups:资源控制的奇妙世界
- 深入剖析 Linux Cgroups 子系统:资源精细管理
- Docker 与 Linux Cgroups:资源隔离的魔法之旅
- 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
- 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络
开发环境如下:
root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal
root@mydocker:~# uname -r
5.4.0-74-generic
注意:需要使用 root 用户
1. 概述
上一篇中基于 overlayfs 实现了容器和宿主机文件系统间的写操作隔离。但是一旦容器退出,容器可读写层的所有内容都会被删除。
那么,如果用户需要持久化容器里的部分数据该怎么办呢?
docker volume 就是用来解决这个问题的。
启动容器时通过-v参数创建 volume 即可实现数据持久化。
本节将会介绍如何实现将宿主机的目录作为数据卷挂载到容器中,并且在容器退出后,数据卷中的内容仍然能够保存在宿主机上。
具体实现主要依赖于 linux 的 bind mount 功能。
bind mount 是一种将一个目录或者文件系统挂载到另一个目录的技术。它允许你在文件系统层级中的不同位置共享相同的内容,而无需复制文件或数。
例如:
mount -o bind /source/directory /target/directory/
这样,/source/directory 中的内容将被挂载到 /target/directory,两者将共享相同的数据。对其中一个目录的更改也会反映到另一个目录。
基于该技术我们只需要将 volume 目录挂载到容器中即可,就像这样:
mount -o bind /host/directory /container/directory/
这样容器中往该目录里写的数据最终会共享到宿主机上,从而实现持久化。
如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。
搜索公众号【探索云原生】即可订阅
2. 实现
volume 功能大致实现步骤如下:
- 1)run 命令增加 -v 参数,格式个 docker 一致
- 例如 -v /etc/conf:/etc/conf 这样
- 2)容器启动前,挂载 volume
- 先准备目录,其次 mount overlayfs,最后 bind mount volume
- 3)容器停止后,卸载 volume
- 先 umount volume,其次 umount overlayfs,最后删除目录
注意:第三步需要先 umount volume ,然后再删除目录,否则由于 bind mount 存在,删除临时目录会导致 volume 目录中的数据丢失。
runCommand
首先在 runCommand 命令中添 -v flag,以接收 volume 参数。
var runCommand = cli.Command{Name: "run",Usage: `Create a container with namespace and cgroups limitmydocker run -it [command]`,Flags: []cli.Flag{cli.BoolFlag{Name: "it", // 简单起见,这里把 -i 和 -t 参数合并成一个Usage: "enable tty",},cli.StringFlag{Name: "mem", // 限制进程内存使用量,为了避免和 stress 命令的 -m 参数冲突 这里使用 -mem,到时候可以看下解决冲突的方法Usage: "memory limit,e.g.: -mem 100m",},cli.StringFlag{Name: "cpu",Usage: "cpu quota,e.g.: -cpu 100", // 限制进程 cpu 使用率},cli.StringFlag{Name: "cpuset",Usage: "cpuset limit,e.g.: -cpuset 2,4", // 限制进程 cpu 使用率},cli.StringFlag{ // 数据卷Name: "v",Usage: "volume,e.g.: -v /ect/conf:/etc/conf",},},/*这里是run命令执行的真正函数。1.判断参数是否包含command2.获取用户指定的command3.调用Run function去准备启动容器:*/Action: func(context *cli.Context) error {if len(context.Args()) < 1 {return fmt.Errorf("missing container command")}var cmdArray []stringfor _, arg := range context.Args() {cmdArray = append(cmdArray, arg)}tty := context.Bool("it")resConf := &subsystems.ResourceConfig{MemoryLimit: context.String("mem"),CpuSet: context.String("cpuset"),CpuCfsQuota: context.Int("cpu"),}log.Info("resConf:", resConf)volume := context.String("v")Run(tty, cmdArray, resConf, volume)return nil},
}
在 Run 函数中,把 volume 传给创建容器的 NewParentProcess 函数和删除容器文件系统的 DeleteWorkSpace 函数。
func Run(tty bool, comArray []string, res *subsystems.ResourceConfig, volume string) {parent, writePipe := container.NewParentProcess(tty, volume)if parent == nil {log.Errorf("New parent process error")return}if err := parent.Start(); err != nil {log.Errorf("Run parent.Start err:%v", err)return}// 创建cgroup manager, 并通过调用set和apply设置资源限制并使限制在容器上生效cgroupManager := cgroups.NewCgroupManager("mydocker-cgroup")defer cgroupManager.Destroy()_ = cgroupManager.Set(res)_ = cgroupManager.Apply(parent.Process.Pid, res)// 在子进程创建后才能通过pipe来发送参数sendInitCommand(comArray, writePipe)_ = parent.Wait()container.DeleteWorkSpace("/root/", volume)
}
NewWorkSpace
在原有创建过程最后增加 volume bind 逻辑:
- 1)首先判断 volume 是否为空,如果为空,就表示用户并没有使用挂载参数,不做任何处理
- 2)如果不为空,则使用 volumeUrlExtract 函数解析 volume 字符串,得到要挂载的宿主机目录和容器目录,并执行 bind mount
func NewWorkSpace(rootPath, volume string) {createLower(rootPath)createDirs(rootPath)mountOverlayFS(rootPath)// 如果指定了volume则还需要mount volumeif volume != "" {mntPath := path.Join(rootPath, "merged")hostPath, containerPath, err := volumeExtract(volume)if err != nil {log.Errorf("extract volume failed,maybe volume parameter input is not correct,detail:%v", err)return}mountVolume(mntPath, hostPath, containerPath)}
}
volumeExtract
语法和 docker run -v 一致,两个路径通过冒号分隔。
// volumeExtract 通过冒号分割解析volume目录,比如 -v /tmp:/tmp
func volumeExtract(volume string) (sourcePath, destinationPath string, err error) {parts := strings.Split(volume, ":")if len(parts) != 2 {return "", "", fmt.Errorf("invalid volume [%s], must split by `:`", volume)}sourcePath, destinationPath = parts[0], parts[1]if sourcePath == "" || destinationPath == "" {return "", "", fmt.Errorf("invalid volume [%s], path can't be empty", volume)}return sourcePath, destinationPath, nil
}
mountVolume
挂载数据卷的过程如下。
- 1)首先,创建宿主机文件目录
- 2)然后,拼接处容器目录在宿主机上的真正目录,格式为:
$mntPath/$containerPath- 因为之前使用了 pivotRoot 将
$mntPath作为容器 rootfs,因此这里的容器目录也可以按层级拼接最终找到在宿主机上的位置。
- 因为之前使用了 pivotRoot 将
- 3)最后,执行 bind mount 操作,至此对数据卷的处理也就完成了。
// mountVolume 使用 bind mount 挂载 volume
func mountVolume(mntPath, hostPath, containerPath string) {// 创建宿主机目录if err := os.Mkdir(hostPath, constant.Perm0777); err != nil {log.Infof("mkdir parent dir %s error. %v", hostPath, err)}// 拼接出对应的容器目录在宿主机上的的位置,并创建对应目录containerPathInHost := path.Join(mntPath, containerPath)if err := os.Mkdir(containerPathInHost, constant.Perm0777); err != nil {log.Infof("mkdir container dir %s error. %v", containerPathInHost, err)}// 通过bind mount 将宿主机目录挂载到容器目录// mount -o bind /hostPath /containerPathcmd := exec.Command("mount", "-o", "bind", hostPath, containerPathInHost)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {log.Errorf("mount volume failed. %v", err)}
}
DeleteWorkSpace
删除容器文件系统时,先判断是否挂载了 volume,如果挂载了则删除时则需要先 umount volume。
注意:一定要要先 umount volume ,然后再删除目录,否则由于 bind mount 存在,删除临时目录会导致 volume 目录中的数据丢失。
func DeleteWorkSpace(rootPath, volume string) {mntPath := path.Join(rootPath, "merged")// 如果指定了volume则需要umount volume// NOTE: 一定要要先 umount volume ,然后再删除目录,否则由于 bind mount 存在,删除临时目录会导致 volume 目录中的数据丢失。if volume != "" {_, containerPath, err := volumeExtract(volume)if err != nil {log.Errorf("extract volume failed,maybe volume parameter input is not correct,detail:%v", err)return}umountVolume(mntPath, containerPath)}umountOverlayFS(mntPath)deleteDirs(rootPath)
}
umountVolume
和普通 umount 一致
func umountVolume(mntPath, containerPath string) {// mntPath 为容器在宿主机上的挂载点,例如 /root/merged// containerPath 为 volume 在容器中对应的目录,例如 /root/tmp// containerPathInHost 则是容器中目录在宿主机上的具体位置,例如 /root/merged/root/tmpcontainerPathInHost := path.Join(mntPath, containerPath)cmd := exec.Command("umount", containerPathInHost)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {log.Errorf("Umount volume failed. %v", err)}
}
3.测试
下面来验证一下程序的正确性。
挂载不存在的目录
第一个实验是把一个宿主机上不存在的文件目录挂载到容器中。
首先还是要在 root 目录准备好 busybox.tar,作为我们的镜像只读层。
$ ls
busybox.tar
启动容器,把宿主机的 /root/volume 挂载到容器的 /tmp 目录下。
root@mydocker:~/feat-volume/mydocker# ./mydocker run -it -v /root/volume:/tmp /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0 }","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"mkdir parent dir /root/volume error. mkdir /root/volume: file exists","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"mkdir container dir /root/merged//tmp error. mkdir /root/merged//tmp: file exists","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"Current location is /root/merged","time":"2024-01-18T16:47:29+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-18T16:47:29+08:00"}
新开一个窗口,查看宿主机 /root 目录:
root@DESKTOP-9K4GB6E:~# ls
busybox busybox.tar merged upper volume work
多了几个目录,其中 volume 就是我们启动容器是指定的 volume 在宿主机上的位置。
同样的,容器中也多了 containerVolume 目录:
/ # ls
bin dev home root tmp var
containerVolume etc proc sys usr
现在往 /tmp 目录写入一个文件
/ # echo KubeExplorer > tmp/hello.txt
/ # ls /tmp
hello.txt
/ # cat /tmp/hello.txt
KubeExplorer
然后查看宿主机的 volume 目录:
root@mydocker:~# ls /root/volume/
hello.txt
root@mydocker:~# cat /root/volume/hello.txt
KubeExplorer
可以看到,文件也在。
然后测试退出容器后是否能持久化。
退出容器:
/ # exit
宿主机中再次查看 volume 目录:
root@mydocker:~# ls /root/volume/
hello.txt
文件还在,说明我们的 volume 功能是正常的。
挂载已经存在目录
第二次实验是测试挂载一个已经存在的目录,这里就把刚才创建的 volume 目录再挂载一次:
root@mydocker:~/feat-volume/mydocker# ./mydocker run -it -v /root/volume:/tmp /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0 }","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"mkdir parent dir /root/volume error. mkdir /root/volume: file exists","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"mkdir container dir /root/merged//tmp error. mkdir /root/merged//tmp: file exists","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"Current location is /root/merged","time":"2024-01-18T17:02:48+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-18T17:02:48+08:00"}
查看刚才的文件是否存在
/ # ls /tmp/hello.txt
/tmp/hello.txt
/ # cat /tmp/hello.txt
KubeExplorer
还在,说明目录确实挂载进去了。
接下来更新文件内容并退出:
/ # echo KubeExplorer222 > /tmp/hello.txt
/ # cat /tmp/hello.txt
KubeExplorer222
/ # exit
在宿主机上查看:
root@mydocker:~# cat /root/volume/hello.txt
KubeExplorer222
至此,说明我们的 volume 功能是正常的。
4. 小结
本篇记录了如何实现 mydocker run -v 参数,增加 volume 以实现容器中部分数据持久化。
一些比较重要的点:
首先要理解 linux 中的 bind mount 功能。
bind mount 是一种将一个目录或者文件系统挂载到另一个目录的技术。它允许你在文件系统层级中的不同位置共享相同的内容,而无需复制文件或数。
其次,则是要理解宿主机目录和容器目录之间的关联关系。
以 -v /root/volume:/tmp 参数为例:
-
1)按照语法,
-v /root/volume:/tmp就是将宿主机/root/volume挂载到容器中的/tmp目录。 -
2)由于前面使用了 pivotRoot 将
/root/merged目录作为容器的 rootfs,因此,容器中的根目录实际上就是宿主机上的/root/merged目录- 第四篇:
-
3)那么容器中的
/tmp目录就是宿主机上的/root/merged/tmp目录。 -
4)因此,我们只需要将宿主机
/root/volume目录挂载到宿主机的/root/merged/tmp目录即可实现 volume 挂载。
在清楚这两部分内容后,整体实现就比较容易理解了。
如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。
搜索公众号【探索云原生】即可订阅

完整代码见:https://github.com/lixd/mydocker
欢迎 Star
相关代码见 feat-volume 分支,测试脚本如下:
需要提前在 /root 目录准备好 busybox.tar 文件,具体见第四篇第二节。
# 克隆代码
git clone -b feat-volume https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 查看文件系统是否变化
./mydocker run -it /bin/ls
./mydocker run -it -v /root/volume:/tmp /bin/sh
相关文章:
从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载
本文为从零开始写 Docker 系列第六篇,实现类似 docker -v 的功能,通过挂载数据卷将容器中部分数据持久化到宿主机。 完整代码见:https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识: …...
网站引用图片但它域名被墙了或者它有防盗链,我们想引用但又不能显示,本文附详细的解决方案非常简单!
最好的办法就是直接读取图片文件,用到php中一个常用的函数file_get_contents(图片地址),意思是读取远程的一张图片,在输出就完事。非常简单~话不多说,直接上代码 <?php header("Content-type: image/jpeg&quo…...
Java八股文(RabbitMQ)
Java八股文のRabbitMQ RabbitMQ RabbitMQ RabbitMQ 是什么?它解决了哪些问题? RabbitMQ 是一个开源的消息代理中间件,用于在应用程序之间进行可靠的异步消息传递。 它解决了应用程序间解耦、消息传递、负载均衡、故障恢复等问题。 RabbitMQ …...
科研学习|论文解读——一种用于短文本消息中的释义检测的深度网络模型(IPM, 2018)
论文原标题 A deep network model for paraphrase detection in short text messages 摘要 本文研究释义检测,即识别语义相同的句子。检测用自然语言编写的相似句子的能力对一些应用程序至关重要,如文本挖掘、文本摘要、剽窃检测、作者身份认证和问题回答。认识到这一…...
鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Web)下篇
onRequestSelected onRequestSelected(callback: () > void) 当Web组件获得焦点时触发该回调。 示例: // xxx.ets import web_webview from ohos.web.webviewEntry Component struct WebComponent {controller: web_webview.WebviewController new web_webv…...
3月19日做题
[NPUCTF2020]验证🐎 if (first && second && first.length second.length && first!second && md5(firstkeys[0]) md5(secondkeys[0]))用数组绕过first1&second[1] 这里正则规律过滤位(Math.) (?:Math(?:\.\w)?) : 匹配 …...
Java8中Stream流API最佳实践Lambda表达式使用示例
文章目录 一、创建流二、中间操作和收集操作筛选 filter去重distinct截取跳过映射合并多个流是否匹配任一元素:anyMatch是否匹配所有元素:allMatch是否未匹配所有元素:noneMatch获取任一元素findAny获取第一个元素findFirst归约数值流的使用中…...
构建Helm chart和chart使用管道与函数简介
目录 一.创建helm chart(以nginx为例) 1.通过create去创建模板 2.查看模板下的文件 3.用chart模版安装nginx 二.版本更新和回滚问题 1.使用upgrade -f values.yaml或者命令行--set来设置 2.查看历史版本并回滚 三.helm模板内管道和函数 1.defau…...
深入理解OnCalculate函数的运行机制
文章目录 一、学习 OnCalculate 函数的运行原理的意义二、OnCalculate 函数原型三、OnCalculate 函数在MT4与MT5区别四、OnCalculate 函数的运行原理 一、学习 OnCalculate 函数的运行原理的意义 OnCalculate函数是MQL语言中的一个重要函数,它用于计算技术指标的值。…...
快速从0-1完成聊天室开发——环信ChatroomUIKit功能详解
聊天室是当下泛娱乐社交应用中最经典的玩法,通过调用环信的 IM SDK 接口,可以快速创建聊天室。如果想根据自己业务需求对聊天室应用的 UI界面、弹幕消息、礼物打赏系统等进行自定义设计,最高效的方式则是使用环信的 ChatroomUIKit 。 文档地址…...
nginx实现多个域名和集群
要通过Nginx实现多个域名和集群,你需要配置Nginx作为反向代理服务器,将来自不同域名的请求转发到集群中的相应后端服务器。下面是一个基本的配置示例,你可以根据自己的需求进行修改和扩展。 首先,确保你已经安装了Nginxÿ…...
C. Left and Right Houses
Problem - C - Codeforces 题目分析 <1>0:想被分割至左边; 1:想被分割至右边 <2>使得左右两侧均有一半及其以上的人满意(我*******) <3>答案若有多个,取最接近中间位置的答案 <4…...
缓存与内存:加速你的Python应用
在现代计算中,缓存和内存是提高程序性能的关键组件。在这篇文章中,我们将深入探讨这两个概念,了解它们是如何工作的,以及如何在Python中有效地使用它们来优化你的程序。 缓存与内存:加速你的Python应用 缓存和内存&…...
Go语言之函数、方法、接口
一、函数 函数的基本语法: func 函数名(形参列表)(返回值列表) {执行语句...return 返回值列表 } 1.形参列表:表示函数的输入 2.函数中的语句:表示为了实现某一功能的代码块 3.函数可以有返回…...
【Week Y2】使用自己的数据集训练YOLO-v5s
Y2-使用自己的数据集训练YOLO-v5s 零、遇到的问题汇总(1)遇到git的import error(2)Error:Dataset not found(3)Error:删除中文后,训练图片路径不存在 一、.xml文件里保存…...
蓝桥杯--基础(哈夫曼)
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner;public class BASIC28 {//哈夫曼书public static void main(String[] args) {Scanner Scannernew Scanner(System.in);int nScanner.nextInt();List<Integer&…...
【Redis内存数据库】NoSQL的特点和应用场景
前言 Redis作为当今最流行的内存数据库,已经成为服务端加速的必备工具之一。 NoSQL数据库采用了非关系型的数据存储模型,能够更好地处理海量数据和高并发访问。 内存数据库具有更快的读写速度和响应时间,因为内存访问速度比磁盘访问速度快…...
JavaScript基础知识2
求数组的最大值案例 let arr[2,6,1,7,400,55,88,100]let maxarr[0]let minarr[0]for(let i1;i<arr.length;i){max<arr[i]?maxarr[i]:maxmin>arr[i]?minarr[i]:min}console.log(最大值是:${max})console.log(最小值是:${min}) 操作数组 修改…...
Linux之线程同步
目录 一、问题引入 二、实现线程同步的方案——条件变量 1、常用接口: 2、使用示例 一、问题引入 我们再次看看上次讲到的多线程抢票的代码:这次我们让一个线程抢完票之后不去做任何事。 #include <iostream> #include <unistd.h> #inc…...
03 龙芯平台openstack部署搭建-keystone部署
#!/bin/bash #创建keystone数据库并授权,可通过mysql -ukeystone -ploongson验证授权登录 mysql -uroot -e “set password for rootlocalhost password(‘loongson’);” mysql -uroot -ploongson -e ‘CREATE DATABASE keystone;’ #本地登录 mysql -uroot -ploo…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
