40分钟学 Go 语言高并发:Select多路复用
Select多路复用
学习目标
知识点 | 掌握程度 | 应用场景 |
---|---|---|
select实现原理 | 深入理解底层机制 | channel通信和多路选择 |
超时处理 | 掌握超时控制方法 | 避免阻塞和资源浪费 |
优先级控制 | 理解优先级实现 | 处理多个channel的顺序 |
性能考虑 | 了解性能优化点 | 高并发场景优化 |
1. Select实现原理
让我们通过一个完整的例子来理解select的工作原理:
package mainimport ("fmt""math/rand""sync""time"
)// 数据生产者
type Producer struct {dataChan chan intdone chan struct{}
}// 创建新的生产者
func NewProducer() *Producer {return &Producer{dataChan: make(chan int, 100),done: make(chan struct{}),}
}// 启动生产
func (p *Producer) Start() {go func() {defer close(p.dataChan)for {select {case <-p.done:fmt.Println("Producer: received stop signal")returndefault:// 生成随机数据data := rand.Intn(100)select {case p.dataChan <- data:fmt.Printf("Producer: sent data %d\n", data)time.Sleep(time.Millisecond * 100)case <-p.done:fmt.Println("Producer: received stop signal while sending")return}}}}()
}// 停止生产
func (p *Producer) Stop() {close(p.done)
}// 获取数据通道
func (p *Producer) DataChan() <-chan int {return p.dataChan
}// 数据处理器
type Processor struct {producers []*Producerresults chan intdone chan struct{}
}// 创建新的处理器
func NewProcessor(producerCount int) *Processor {producers := make([]*Producer, producerCount)for i := 0; i < producerCount; i++ {producers[i] = NewProducer()}return &Processor{producers: producers,results: make(chan int, producerCount*100),done: make(chan struct{}),}
}// 启动处理
func (p *Processor) Start() {// 启动所有生产者for i, producer := range p.producers {producer.Start()// 为每个生产者启动一个处理goroutinego func(id int, prod *Producer) {for {select {case data, ok := <-prod.DataChan():if !ok {fmt.Printf("Processor %d: producer channel closed\n", id)return}// 处理数据result := data * 2select {case p.results <- result:fmt.Printf("Processor %d: processed data %d -> %d\n", id, data, result)case <-p.done:return}case <-p.done:fmt.Printf("Processor %d: received stop signal\n", id)return}}}(i, producer)}
}// 停止处理
func (p *Processor) Stop() {close(p.done)for _, producer := range p.producers {producer.Stop()}
}// 获取结果通道
func (p *Processor) Results() <-chan int {return p.results
}func main() {// 创建有3个生产者的处理器processor := NewProcessor(3)// 启动处理器processor.Start()// 创建结果收集器var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()count := 0for result := range processor.Results() {fmt.Printf("Collector: received result %d\n", result)count++if count >= 20 { // 收集20个结果后停止processor.Stop()break}}}()// 等待处理完成wg.Wait()fmt.Println("Main: processing completed")
}
1.1 Select执行流程图
2. 超时处理
让我们实现一个带有超时控制的服务请求处理系统:
package mainimport ("context""fmt""math/rand""sync""time"
)// 请求处理器
type RequestHandler struct {requests chan Requestresponses chan Responsedone chan struct{}wg sync.WaitGroup
}// 请求结构
type Request struct {ID intTimeout time.DurationData string
}// 响应结构
type Response struct {RequestID intResult stringError error
}// 创建新的请求处理器
func NewRequestHandler() *RequestHandler {return &RequestHandler{requests: make(chan Request, 100),responses: make(chan Response, 100),done: make(chan struct{}),}
}// 启动处理器
func (h *RequestHandler) Start(workers int) {for i := 0; i < workers; i++ {h.wg.Add(1)go h.worker(i)}
}// 工作协程
func (h *RequestHandler) worker(id int) {defer h.wg.Done()for {select {case req, ok := <-h.requests:if !ok {fmt.Printf("Worker %d: request channel closed\n", id)return}// 创建context用于超时控制ctx, cancel := context.WithTimeout(context.Background(), req.Timeout)// 处理请求response := h.processRequest(ctx, req)// 发送响应select {case h.responses <- response:fmt.Printf("Worker %d: sent response for request %d\n", id, req.ID)case <-h.done:cancel()return}cancel() // 清理contextcase <-h.done:fmt.Printf("Worker %d: received stop signal\n", id)return}}
}// 处理单个请求
func (h *RequestHandler) processRequest(ctx context.Context, req Request) Response {// 模拟处理时间processTime := time.Duration(rand.Intn(int(req.Timeout))) + req.Timeout/2select {case <-time.After(processTime):return Response{RequestID: req.ID,Result: fmt.Sprintf("Processed: %s", req.Data),}case <-ctx.Done():return Response{RequestID: req.ID,Error: ctx.Err(),}}
}// 提交请求
func (h *RequestHandler) SubmitRequest(req Request) error {select {case h.requests <- req:return nilcase <-h.done:return fmt.Errorf("handler is stopped")}
}// 获取响应
func (h *RequestHandler) GetResponse() (Response, error) {select {case resp := <-h.responses:return resp, nilcase <-h.done:return Response{}, fmt.Errorf("handler is stopped")}
}// 停止处理器
func (h *RequestHandler) Stop() {close(h.done)h.wg.Wait()close(h.requests)close(h.responses)
}func main() {// 创建请求处理器handler := NewRequestHandler()handler.Start(3)// 发送一些测试请求requests := []Request{{ID: 1, Timeout: time.Second, Data: "Fast request"},{ID: 2, Timeout: time.Second * 2, Data: "Normal request"},{ID: 3, Timeout: time.Millisecond * 500, Data: "Quick request"},{ID: 4, Timeout: time.Second * 3, Data: "Slow request"},}// 提交请求for _, req := range requests {if err := handler.SubmitRequest(req); err != nil {fmt.Printf("Failed to submit request %d: %v\n", req.ID, err)continue}fmt.Printf("Submitted request %d\n", req.ID)}// 收集响应var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()for i := 0; i < len(requests); i++ {resp, err := handler.GetResponse()if err != nil {fmt.Printf("Failed to get response: %v\n", err)continue}if resp.Error != nil {fmt.Printf("Request %d failed: %v\n", resp.RequestID, resp.Error)} else {fmt.Printf("Request %d succeeded: %s\n", resp.RequestID, resp.Result)}}}()// 等待所有响应处理完成wg.Wait()// 停止处理器handler.Stop()fmt.Println("Main: processing completed")
}
3. 优先级控制
让我们实现一个带有优先级控制的任务调度系统:
package mainimport ("fmt""math/rand""sort""sync""time"
)// 优先级级别
const (PriorityHigh = iotaPriorityMediumPriorityLow
)// 任务结构
type Task struct {ID intPriority intAction func() error
}// 优先级调度器
type PriorityScheduler struct {highPriority chan TaskmediumPriority chan TasklowPriority chan Taskresults chan errordone chan struct{}wg sync.WaitGroup
}// 创建新的调度器
func NewPriorityScheduler() *PriorityScheduler {return &PriorityScheduler{highPriority: make(chan Task, 100),mediumPriority: make(chan Task, 100),lowPriority: make(chan Task, 100),results: make(chan error, 100),done: make(chan struct{}),}
}// 启动调度器
func (s *PriorityScheduler) Start(workers int) {for i := 0; i < workers; i++ {s.wg.Add(1)go s.worker(i)}
}// 工作协程
func (s *PriorityScheduler) worker(id int) {defer s.wg.Done()for {// 使用优先级顺序处理任务select {case <-s.done:return// 高优先级任务case task := <-s.highPriority:fmt.Printf("Worker %d: processing high priority task %d\n", id, task.ID)s.results <- task.Action()// 如果没有高优先级任务,检查中优先级default:select {case <-s.done:returncase task := <-s.highPriority:fmt.Printf("Worker %d: processing high priority task %d\n", id, task.ID)s.results <- task.Action()case task := <-s.mediumPriority:fmt.Printf("Worker %d: processing medium priority task %d\n", id, task.ID)s.results <- task.Action()// 如果没有中优先级任务,检查低优先级default:select {case <-s.done:returncase task := <-s.highPriority:fmt.Printf("Worker %d: processing high priority task %d\n", id, task.ID)s.results <- task.Action()case task := <-s.mediumPriority:fmt.Printf("Worker %d: processing medium priority task %d\n", id, task.ID)s.results <- task.Action()case task := <-s.lowPriority:fmt.Printf("Worker %d: processing low priority task %d\n", id, task.ID)s.results <- task.Action()}}}}
}// 提交任务
func (s *PriorityScheduler) SubmitTask(task Task) error {var targetChan chan Taskswitch task.Priority {case PriorityHigh:targetChan = s.highPrioritycase PriorityMedium:targetChan = s.mediumPrioritycase PriorityLow:targetChan = s.lowPrioritydefault:return fmt.Errorf("invalid priority level: %d", task.Priority)}select {case targetChan <- task:return nilcase <-s.done:return fmt.Errorf("scheduler is stopped")}
}// 获取结果
func (s *PriorityScheduler) Results() <-chan error {return s.results
}// 停止调度器
func (s *PriorityScheduler) Stop() {close(s.done)s.wg.Wait()close(s.highPriority)close(s.mediumPriority)close(s.lowPriority)close(s.results)
}// 创建模拟任务
func createTask(id int, priority int, duration time.Duration) Task {return Task{ID: id,Priority: priority,Action: func() error {time.Sleep(duration)if rand.Float32() < 0.1 { // 10%的失败率return fmt.Errorf("task %d failed", id)}return nil},}
}func main() {// 创建调度器scheduler := NewPriorityScheduler()scheduler.Start(3)// 创建一些测试任务var tasks []Taskfor i := 0; i < 15; i++ {priority := i % 3 // 在三个优先级之间循环duration := time.Millisecond * time.Duration(rand.Intn(500)+100)tasks = append(tasks, createTask(i, priority, duration))}// 随机打乱任务顺序rand.Shuffle(len(tasks), func(i, j int) {tasks[i], tasks[j] = tasks[j], tasks[i]})// 提交任务for _, task := range tasks {if err := scheduler.SubmitTask(task); err != nil {fmt.Printf("Failed to submit task %d: %v\n", task.ID, err)continue}fmt.Printf("Submitted task %d with priority %d\n", task.ID, task.Priority)}// 收集结果var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()completed := 0failures := 0for err := range scheduler.Results() {if err != nil {failures++fmt.Printf("Task failed: %v\n", err)}completed++if completed >= len(tasks) {break}}fmt.Printf("\nProcessing completed: %d tasks total, %d failures\n",completed, failures)}()// 等待所有任务完成wg.Wait()// 停止调度器scheduler.Stop()fmt.Println("Main: scheduler stopped")
}
让我们继续完成优先级控制的示例代码:
3.1 优先级控制流程图
4. 性能考虑
4.1 Select性能优化建议
- case数量控制
- select中的case数量会影响性能
- 建议控制在合理范围内(通常不超过5-10个)
- channel缓冲区
- 适当使用带缓冲的channel可以提高性能
- 避免频繁的阻塞和唤醒
- default分支使用
- 合理使用default避免无谓的阻塞
- 考虑轮询间隔,避免CPU空转
让我们实现一个性能优化的示例:
package mainimport ("fmt""runtime""sync""sync/atomic""time"
)// 性能统计
type Stats struct {processed uint64dropped uint64blocked uint64
}// 批处理器
type BatchProcessor struct {input chan interface{}output chan []interface{}done chan struct{}stats *StatsbatchSize intmaxWait time.Duration
}// 创建新的批处理器
func NewBatchProcessor(batchSize int, maxWait time.Duration) *BatchProcessor {return &BatchProcessor{input: make(chan interface{}, batchSize*2),output: make(chan []interface{}, batchSize),done: make(chan struct{}),stats: &Stats{},batchSize: batchSize,maxWait: maxWait,}
}// 启动处理
func (p *BatchProcessor) Start(workers int) {for i := 0; i < workers; i++ {go p.worker(i)}// 启动统计打印go p.printStats()
}// 工作协程
func (p *BatchProcessor) worker(id int) {batch := make([]interface{}, 0, p.batchSize)timer := time.NewTimer(p.maxWait)defer timer.Stop()for {// 重置计时器if !timer.Stop() {select {case <-timer.C:default:}}timer.Reset(p.maxWait)// 优化的批处理逻辑for len(batch) < p.batchSize {select {case <-p.done:returncase item := <-p.input:batch = append(batch, item)atomic.AddUint64(&p.stats.processed, 1)case <-timer.C:// 达到最大等待时间,处理当前批次if len(batch) > 0 {p.processBatch(batch)batch = batch[:0]}atomic.AddUint64(&p.stats.blocked, 1)continuedefault:// 如果输入队列为空且已有数据,立即处理if len(batch) > 0 {p.processBatch(batch)batch = batch[:0]}// 短暂休眠避免CPU空转runtime.Gosched()continue}// 批次满了就处理if len(batch) >= p.batchSize {p.processBatch(batch)batch = batch[:0]}}}
}// 处理批次数据
func (p *BatchProcessor) processBatch(batch []interface{}) {// 创建副本避免数据竞争output := make([]interface{}, len(batch))copy(output, batch)// 尝试发送处理结果select {case p.output <- output:// 成功发送default:// 输出channel满了,增加丢弃计数atomic.AddUint64(&p.stats.dropped, uint64(len(batch)))}
}// 提交数据
func (p *BatchProcessor) Submit(item interface{}) error {select {case p.input <- item:return nilcase <-p.done:return fmt.Errorf("processor is stopped")default:atomic.AddUint64(&p.stats.dropped, 1)return fmt.Errorf("input channel full")}
}// 获取输出通道
func (p *BatchProcessor) Output() <-chan []interface{} {return p.output
}// 定期打印统计信息
func (p *BatchProcessor) printStats() {ticker := time.NewTicker(time.Second)defer ticker.Stop()var lastProcessed, lastDropped, lastBlocked uint64for {select {case <-p.done:returncase <-ticker.C:processed := atomic.LoadUint64(&p.stats.processed)dropped := atomic.LoadUint64(&p.stats.dropped)blocked := atomic.LoadUint64(&p.stats.blocked)fmt.Printf("Stats - Processed: %d/s, Dropped: %d/s, Blocked: %d/s\n",processed-lastProcessed,dropped-lastDropped,blocked-lastBlocked)lastProcessed = processedlastDropped = droppedlastBlocked = blocked}}
}// 停止处理器
func (p *BatchProcessor) Stop() {close(p.done)
}func main() {// 创建批处理器processor := NewBatchProcessor(100, time.Millisecond*50)processor.Start(3)// 模拟高速数据提交var wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1)go func(id int) {defer wg.Done()for j := 0; j < 10000; j++ {data := fmt.Sprintf("Data-%d-%d", id, j)processor.Submit(data)time.Sleep(time.Microsecond * time.Duration(50+id*10))}}(i)}// 处理输出go func() {for batch := range processor.Output() {// 这里可以进行批量处理,比如写入数据库fmt.Printf("Received batch of size %d\n", len(batch))}}()// 等待提交完成wg.Wait()time.Sleep(time.Second) // 等待最后的处理完成// 停止处理器processor.Stop()fmt.Println("Main: processing completed")
}
4.2 性能优化要点
- 避免过度使用select
- 只在必要的地方使用select
- 考虑其他并发控制方式
- channel设计优化
- 合理设置缓冲区大小
- 避免频繁的channel创建和关闭
- goroutine管理
- 控制goroutine数量
- 实现优雅的退出机制
- 内存优化
- 重用切片和对象
- 避免不必要的内存分配
总结
核心要点
- Select实现原理
- 随机选择机制
- 阻塞和非阻塞模式
- 多路复用特性
- 超时处理
- 超时控制方法
- 资源释放保证
- 错误处理机制
- 优先级控制
- 优先级实现方式
- 任务调度策略
- 公平性保证
- 性能优化
- select使用建议
- channel优化
- 资源管理
怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!
相关文章:

40分钟学 Go 语言高并发:Select多路复用
Select多路复用 学习目标 知识点掌握程度应用场景select实现原理深入理解底层机制channel通信和多路选择超时处理掌握超时控制方法避免阻塞和资源浪费优先级控制理解优先级实现处理多个channel的顺序性能考虑了解性能优化点高并发场景优化 1. Select实现原理 让我们通过一个…...

candence: 如何快速设置SUBCLASS 的颜色
如何快速设置SUBCLASS 的颜色 一、一般操作 正常情况下修改SUBCLASS,需要如下步骤进行设置: 二、快速操作 右键,选择一个颜色即可...

FinalShell进行前端项目部署及nginx配置
首先需要准备服务器(阿里云、腾讯云都可)与域名; 示例为阿里云服务器; 1.进行FinalShell下载 下载官网 https://www.hostbuf.com/ 2.下载完毕后 配置FinalShell ssh 名称自定义即可! 2-1 提示连接成功 3.首先检查nginx是否下载 …...

神经网络(系统性学习一):入门篇——简介、发展历程、应用领域、基本概念、超参数调优、网络类型分类
相关文章: 神经网络中常用的激活函数 神经网络简介 神经网络(Neural Networks)是受生物神经系统启发而设计的数学模型,用于模拟人类大脑处理信息的方式。它由大量的节点(或称为“神经元”)组成࿰…...

用nextjs开发时遇到的问题
这几天已经基本把node后端的接口全部写完了,在前端开发时考虑时博客视频类型,考虑了ssr,于是选用了nextJs,用的是nextUi,tailwincss,目前碰到两个比较难受的事情。 1.nextUI个别组件无法在服务器段渲染 目前简单的解决方法&…...
微前端基础知识入门篇(二)
概述 在上一篇介绍了一些微前端的基础知识,详见微前端基础知识入门篇(一)。本文主要介绍qiankun微前端框架的实战入门内容。 qiankun微前端实践 通过Vite脚手架分别创建三个程序,主应用A为:vite+vue3+ts,两个微应用分别为B:vite+vue3+ts;C:vite+React+ts。因为qiankun的…...

自然语言处理:第六十五章 MinerU 开源PDF文档解析方案
本人项目地址大全:Victor94-king/NLP__ManVictor: CSDN of ManVictor 原文地址:MinerU:精准解析PDF文档的开源解决方案 论文链接:MinerU: An Open-Source Solution for Precise Document Content Extraction git地址࿱…...
Arcpy 多线程批量重采样脚本
Arcpy 多线程批量重采样脚本 import arcpy import os import multiprocessingdef resample_tifs(input_folder, output_folder, cell_size0.05, resampling_type"BILINEAR"):"""将指定文件夹下的所有 TIFF 文件重采样到指定分辨率,并输出…...

python 画图例子
目录 多组折线图点坐标的折线图 多组折线图 数据: 第1行为x轴标签第2/3/…行等为数据,其中第一列为标签,后面为y值 图片: 代码: import matplotlib.pyplot as plt# 原始数据字符串 # 第1行为x轴标签 # 第2/3/...行等为数据,其中第一列为标签,后面…...

Win11 22H2/23H2系统11月可选更新KB5046732发布!
系统之家11月22日报道,微软针对Win11 22H2/23H2版本推送了2024年11月最新可选更新补丁KB5046732,更新后,系统版本号升至22621.4541和22631.4541。本次更新后系统托盘能够显示缩短的日期和时间,文件资源管理器窗口很小时搜索框被切…...

【STM32】MPU6050初始化常用寄存器说明及示例代码
一、MPU6050常用配置寄存器 1、电源管理寄存器1( PWR_MGMT_1 ) 此寄存器允许用户配置电源模式和时钟源。 DEVICE_RESET :用于控制复位的比特位。设置为1时复位 MPU6050,内部寄存器恢复为默认值,复位结束…...

深度学习中的mAP
在深度学习中,mAP是指平均精度均值(mean Average Precision),它是深度学习中评价模型好坏的一种指标(metric),特别是在目标检测中。 精确率和召回率的概念: (1).精确率(Precision):预测阳性结果中实际正确的比例(TP / …...

Redis设计与实现 学习笔记 第二十章 Lua脚本
Redis从2.6版本引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令。 其中EVAL命令可以直接对输入的脚本进行求值: 而使用EVALSHA命令则可以根据脚本的SHA1校验…...

大模型(LLMs)推理篇
大模型(LLMs)推理篇 1. 为什么大模型推理时显存涨的那么多还一直占着? 首先,序列太长了,有很多Q/K/V;其次,因为是逐个预测next token,每次要缓存K/V加速解码。 大模型在gpu和cpu上…...
Leetcode 412. Fizz Buzz
Problem Given an integer n, return a string array answer (1-indexed) where: answer[i] “FizzBuzz” if i is divisible by 3 and 5.answer[i] “Fizz” if i is divisible by 3.answer[i] “Buzz” if i is divisible by 5.answer[i] i (as a string) if none of t…...

双因子认证:统一运维平台安全管理策略
01双因子认证概述 双因子认证(Two-Factor Authentication,简称2FA)是一种身份验证机制,它要求用户提供两种不同类型的证据来证明自己的身份。这通常包括用户所知道的(如密码)、用户所拥有的(如…...

CMake笔记:install(TARGETS target,...)无法安装的Debug/lib下
1. 问题描述 按如下CMake代码,无法将lib文件安装到Debug/lib或Release/lib目录下,始终安装在CMAKE_INSTALL_PREFIX/lib下。 install(TARGETS targetCONFIGURATIONS DebugLIBRARY DESTINATION Debug/lib) install(TARGETS targetCONFIGURATIONS Release…...

使用ENSP实现NAT
一、项目拓扑 二、项目实现 1.路由器AR1配置 进入系统试图 sys将路由器命名为R1 sysname R1关闭信息中心 undo info-center enable进入g0/0/0接口 int g0/0/0将g0/0/0接口IP地址配置为12.12.12.1/30 ip address 12.12.12.1 30进入e0/0/1接口 int g0/0/1将g0/0/1接口IP地址配置…...

漫步北京小程序构建智慧出行,打造旅游新业态模式
近年来,北京市气象服务中心持续加强推进旅游气象服务,将旅游气象监测预警基础设施纳入景区配套工程,提升气象和旅游融合发展水平,服务建设高品质智慧旅游强市。 天气条件往往影响着旅游景观的体验,北京万云科技有限公…...

对齐输出
对齐输出 C语言代码C 语言代码Java语言代码Python语言代码 💐The Begin💐点点关注,收藏不迷路💐 输入三个整数,按每个整数占8个字符的宽度,右对齐输出它们。 输入 只有一行,包含三个整数&…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...

Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...

五、jmeter脚本参数化
目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …...