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

Go语言的100个错误使用场景(55-60)|并发基础

前言

大家好,这里是白泽。**《Go语言的100个错误以及如何避免》**是最近朋友推荐我阅读的书籍,我初步浏览之后,大为惊喜。就像这书中第一章的标题说到的:“Go: Simple to learn but hard to master”,整本书通过分析100个错误使用 Go 语言的场景,带你深入理解 Go 语言。

我的愿景是以这套文章,在保持权威性的基础上,脱离对原文的依赖,对这100个场景进行篇幅合适的中文讲解。所涉内容较多,总计约 8w 字,这是该系列的第七篇文章,对应书中第55-60个错误场景。

🌟 当然,如果您是一位 Go 学习的新手,您可以在我开源的学习仓库中,找到针对**《Go 程序设计语言》**英文书籍的配套笔记,其他所有文章也会整理收集在其中。

📺 B站:白泽talk,公众号【白泽talk】,聊天交流群:622383022,原书电子版可以加群获取。

前文链接:

  • 《Go语言的100个错误使用场景(1-10)|代码和项目组织》

  • 《Go语言的100个错误使用场景(11-20)|项目组织和数据类型》

  • 《Go语言的100个错误使用场景(21-29)|数据类型》

  • 《Go语言的100个错误使用场景(30-40)|数据类型与字符串使用》

  • 《Go语言的100个错误使用场景(40-47)|字符串&函数&方法》

  • 《Go语言的100个错误使用场景(48-54)|错误管理》

8. 并发基础

🌟 章节概述

  • 理解并发和并行
  • 为什么并发并不总是更快
  • cup 负载和 io 负载的影响
  • 使用 channel 对比使用互斥锁
  • 理解数据竞争和竞态条件的区别
  • 使用 Go context

8.1 混淆并发与并行的概念(#55)

以一家咖啡店的运作为例讲解一下并发和并行的概念。

image-20240224103434660

  • 并行:强调执行,如两个咖啡师同时在给咖啡拉花
  • 并发:两个咖啡师竞争一个咖啡研磨机器的使用

8.2 认为并发总是更快(#56)

  • 线程:OS 调度的基本单位,用于调度到 CPU 上执行,线程的切换是一个高昂的操作,因为要求将当前 CPU 中运行态的线程上下文保存,切换到可执行态,同时调度一个可执行态的线程到 CPU 中执行。
  • 协程:线程由 OS 上下文切换 CPU 内核,而 Goroutine 则由 Go 运行时上下文切换协程。Go 协程占用内存比线程少(2KB/2MB),协程的上下文切换比线程快80~90%。

🌟 GMP 模型:

  • G:Goroutine
    • 执行态:被调度到 M 上执行
    • 可执行态:等待被调度
    • 等待态:因为一些原因被阻塞
  • M:OS thread
  • P:CPU core
    • 每个 P 有一个本地 G 队列(任务队列)
    • 所有 P 有一个公共 G 队列(任务队列)

协程调度规则:每一个 OS 线程(M)被调度到 P 上执行,然后每一个 G 运行在 M 上。

image-20240224111521402

🌟 上图中展示了一个4核 CPU 的机器调度 Go 协程的场景:

此时 P2 正在闲置因为 M3 执行完毕释放了对 P2 的占用,虽然 P2 的 Local queue 中已经空了,没有 G 可以调度执行,但是每隔一定时间,Go runtime 会去 Global queue 和其他 P 的 local queue 偷取一些 G 用于调度执行(当前存在6个可执行的G)。

特别的,在 Go1.14 之前,Go 协程的调度是合作形式的,因此 Go 协程发生切换的只会因为阻塞等待(IO/channel/mutex等),但 Go1.14 之后,运行时间超过 10ms 的协程会被标记为可抢占,可以被其他协程抢占 P 的执行。

🌟 为了印证有时候多协程并不一定会提高性能,这里以归并排序为例举三个例子:

image-20240224232145909

示例一:

func sequentialMergesort(s []int) {if len(s) <= 1 {return}middle := len(s) / 2sequentialMergesort(s[:middle])sequentialMergesort(s[middle:])merge(s, middle)
}func merge(s []int, middle int) {// ...
}

示例二:

func sequentialMergesortV1(s []int) {if len(s) <= 1 {return}middle := len(s) / 2var wg sync.WaitGroup()wg.Add(2)go func() {defer wd.Done()parallelMergesortV1(s[:middle])}()go func() {defer wd.Done()parallelMergesortV1(s[middle:])}()wg.Wait()merge(s, middle)
}

示例三:

const max = 2048func sequentialMergesortV2(s []int) {if len(s) <= 1 {return}if len(s) < max {sequentialMergesort(s)} else {middle := len(s) / 2var wg sync.WaitGroup()wg.Add(2)go func() {defer wd.Done()parallelMergesortV2(s[:middle])}()go func() {defer wd.Done()parallelMergesortV2(s[middle:])}()wg.Wait()merge(s, middle)   }
}

由于创建协程和调度协程本身也有开销,第二种情况无论多少个元素都使用协程去进行并行排序,导致归并很少的元素也需要创建协程和调度,开销比排序更多,导致性能还比不上第一种顺序归并。

而在本台电脑上,经过调试第三种方式可以获得比第一种方式更优的性能,因为它在元素大于2048个的时候,选择并行排序,而少于则使用顺序排序。但是2048是一个魔法数,不同电脑上可能不同。这里这是为了证明,完全依赖并发/并行的机制,并不一定会提高性能,需要注意协程本身的开销。

8.3 分不清何时使用互斥锁或 channel(#57)

image-20240225155219930

  • mutex:针对 G1 和 G2 这种并行执行的两个协程,它们可能会针对同一个对象进行操作,比如切片。此时是一个发生资源竞争的场景,因此适合使用互斥锁。
  • channel:而上游的 G1 或者 G2 中任何一个都可以在执行完自己逻辑之后,通知 G3 开始执行,或者传递给 G3 某些处理结果,此时使用 channel,因为 Go 推荐使用 channel 作为协程间通信的手段。

8.4 不理解竞态问题(#58)

🌟 数据竞争:多个协程同时访问一块内存地址,且至少有一次写操作。

假设有两个并发协程对 i 进行自增操作:

i := 0go func() {i++
}()go func() {i++
}()

因为 i++ 操作可以被分解为3个步骤:

  1. 读取 i 的值
  2. 对应值 + 1
  3. 将值写会 i

当并发执行两个协程的时候,i 的最终结果是无法预计的,可能为1,也可能为2。

修正方案一:

var i int64go func() {atomic.AddInt64(&i, 1)  
}()go func() {atomic.AddInt64(&i, 1)  
}()

使用 sync/atomic 包的原子运算,因为原子运算不能被打断,因此两个协程无法同时访问 i,因为客观上两个协程按顺序执行,因此最终的结果为2。

但是因为 Go 语言只为几种类型提供了原子运算,无法应对 slices、maps、structs。

修正方案二:

i := 0
mutex := sync.Mutex{}go func() {mutex.Lock()i++mutex.UnLock()
}()go func() {mutex.Lock()i++mutex.UnLock()
}()

此时被 mutex 包裹的部分,同一时刻只能允许一个协程访问。

修正方案三:

i := 0
ch := make(chan int)go func() {ch <- 1
}go func() {ch <- 1
}i += <-ch
i += <-ch

使用阻塞的 channel,主协程必须从 ch 中读取两次才能执行结束,因此结果必然是2。

🌟 Go 语言的内存模型

我们使用 A < B 表示事件 A 发生在事件 B 之前。

i := 0
go func() {i++
}()

因为创建协程发生在协程的执行,因此读取变量 i 并给 i + 1在这个例子中不会造成数据竞争。

i := 0
go func() {i++
}()
fmt.Println(i)

协程的退出无法保证一定发生在其他事件之前,因此这个例子会发生数据竞争。

i := 0
ch := make(chan struct{})
go func() {<-chfmt.Println(i)
}()
i++
ch <- struct{}{}

这个例子由于打印 i 之前,一定会执行 i++ 的操作,并且子协程等待主协程的 channel 的解除阻塞信号。

i := 0
ch := make(chan struct{})
go func() {<-chfmt.Println(i)
}()
i++
close()

和上一个例子有点像,channel 在关闭事件发生在从 channel 中读取信号之前,因此不会发生数据竞争。

i := 0
ch := make(chan struct{}, 1)
go func() {i = 1<-ch
}()
ch <- struct{}{}
fmt.Println(i)

主协程向 channel 放入值的操作执行,并不能确保与子协程的执行事件顺序,因此会发生数据竞争。

i := 0
ch := make(chan struct{})
go func() {i = 1<-ch
}()
ch <- struct{}{}
fmt.Println(i)

主协程的存入 channel 的事件,必然发生在子协程从 channel 取出事件之前,因此不会发生数据竞争。

i := 0
ch := make(chan struct{})
go func() {i = 1<-ch
}()
ch <- struct{}{}
fmt.Println(i)

无无缓冲的 channel 确保在主协程执行打印事件之前,必须会执行 i = 1 的赋值操作,因此不会发生数据竞争。

8.5 不了解工作负载类型对并发性能的影响(#59)

🌟 工作负载执行时间受到下述条件影响:

  • CPU 执行速度:例如执行归并排序,此时工作负载称作——CPU约束。
  • IO 执行速度:对DB进行查询,此时工作负载称作——IO约束。
  • 可用内存:此时工作负载称作——内存约束。

🌟 接下来通过一个场景讲解为何讨论并发性能,需要区分负载类型:假设有一个 read 函数,从循环中每次读取1024字节,然后将获得的内容传递给一个 task 函数执行,返回一个 int 值,并每次循环对这个 int 进行求和。

串行实现:

func read(r io.Reader) (int, error) {count := 0for {b := make([]byte, 1024)_, err := r.Read(b)if err != nil {if err == io.EOF {break}return 0, err}count += task(b)}return count, nil
}

并发实现:Worker pooling pattern(工作池模式)是一种并发设计模式,用于管理一组固定数量的工作线程(worker threads)。这些工作线程从一个共享的工作队列中获取任务,并执行它们。这个模式的主要目的是提高并发性能,通过减少线程的创建和销毁,以及通过限制并发执行的任务数量来避免资源竞争。

image-20240227225024934

func read(r io.Reader) (int, error) {var count int64wg := sync.WaitGroup{}var n = 10ch := make(chan []byte, n)wg.Add(n)for i := 0; i < n; i++ {go func() {defer wg.Done()for b := range ch {v := tasg(b)atomic.AddInt64(&count, int64(v))}}()}for {b := make([]byte, 1024)ch <- b}close(ch)wg.Wait()return int(count), nil
}

这个例子中,关键在于如何确定 n 的大小:

  • 如果工作负载被 IO 约束:则 n 取决于外部系统,使得系统获得最大吞吐量的并发数。
  • 如果工作负载被 CPU 约束:最佳实践是取决于 GOMAXPROOCS,这是一个变量存放系统允许分配给执行协程的最大线程数量,默认情况下,这个变量用于设置逻辑 CPU 的数量,因为理想状态下,只能允许最大线程数量的协程同时执行,

image-20240228214951485

8.6 不懂得使用 Go contexts(#60)

🌟 A Context carries a deadline, a cancellation signal, and other values across API boundaries.

截止时间

  • time.Duration(250ms)
  • time.Time(2024-02-28 00:00:00 UTC)

当截止时间到达的时候,一个正在执行的行为将停止。(如IO请求,等待从 channel 中读取消息)

假设有一个雷达程序,每隔四秒钟,向其他应用提供坐标坐标信息,且只关心最新的坐标。

type publisher interface {Publish(ctx context.Content, position flight.Position) error
}type publishHandler struct {pub publisher
}func (h publishHandler) publishPosition(position flight.Position) error {ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)defer cancel()return h.pub.Publish(ctx, position)
}

通过上述代码,创建一个过期时间4秒中的 context 上下文,则应用可以通过判断 ctx.Done() 判断这个上下文是否过期或者被取消,从而判断是否为4秒内的有效坐标。

cancel() 在 return 之前调用,则可以通过 cancel 方法关闭上下文,避免内存泄漏。

取消信号

func main() {ctx. cancel := context.WithCancel(context.Background())defer cacel()go func() {CreateFileWatcher(ctx, "foo.txt") }()
}

在 main 方法执行完之前,通过调用 cancel 方法,将 ctx 的取消信号传递给 CreateFileWatcher() 函数。

上下文传递值

ctx := context.WithValue(context.Background(), "key", "value")
fmt.Println(ctx.Value("key"))# value

key 和 value 是 any 类型的。

package providertype key stringconst myCustomKey key = "key"func f(ctx context.Context) {ctx = context.WithValue(ctx, myCustomKey, "foo")// ...
}

为了避免两个不同的 package 对同一个 ctx 存入同样的 key 导致冲突,可以将 key 设置成不允许导出的类型。

一些用法:

  1. 在借助 ctx 在函数之间传递同一个 id,实现链路追踪。
  2. 借助 ctx 在多个中间件之间传递,存放处理信息。
type key stringconst inValidHostKey key = "isValidHost"func checkValid(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {validHost := r.Host == "came"ctx := context.WithValue(r.Context(), inValidHostKey, validHost)next.ServeHTTP(w, r.WithContext(ctx))})
}

checkValid 作为一个中间件,优先处理 http 请求,将处理结果存放在 ctx 中,传递给下一个处理步骤。

捕获 context 取消

context.Context 类型提供了一个 Done 方法,返回了一个接受关闭信号的 channel:<-chan struct{},触发条件如下:

  • 如果 ctx 通过 context.WithCancel 创建,则可以通过 cancel 函数关闭。
  • 如果 ctx 通过 context.WithDeadline 创建,当过期的时候 channel 关闭。

此外,context.Context 提供了一个 Err 方法,将返回导致 channel 关闭的原因,如果没有关闭,调用则返回 nil。

  • 返回 context.Canceled error 如果 channel 被 cancel 方法关闭。
  • 返回 context.DeadlineExceeded 如果达到 deadline 过期。
func handler(ctx context.Context, ch chan Message) error {for {select {case msg := <-ch:// Do something with msgcase <-ctx.Done():return ctx.Err()}}
}

小结

你已完成《Go语言的100个错误》全书学习进度60%,欢迎追更。

相关文章:

Go语言的100个错误使用场景(55-60)|并发基础

前言 大家好&#xff0c;这里是白泽。**《Go语言的100个错误以及如何避免》**是最近朋友推荐我阅读的书籍&#xff0c;我初步浏览之后&#xff0c;大为惊喜。就像这书中第一章的标题说到的&#xff1a;“Go: Simple to learn but hard to master”&#xff0c;整本书通过分析1…...

钉钉机器人发送折线图卡片 工具类代码

钉钉机器人 “创建并投放卡片 接口 ” 可以 发送折线图、柱状图 官方文档&#xff1a;创建并投放卡片 - 钉钉开放平台 0依赖、1模板、2机器人放到内部应用、3放开这个权限 、4工具类、5调用工具类 拼接入参 卡片模板 自己看文档创建&#xff0c;卡片模板的id 有用 0、依赖…...

基于springboot的大型商场应急预案管理系统论文

大型商场应急预案管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了大型商场应急预案管理系统的开发全过程。通过分析大型商场应急预案管理系统管理的不足&#xff0c;创建了一个计算机管理大型商场应急…...

强化学习嵌入Transformer(代码实践)

这里写目录标题 ChatGPT的答案GPT4.0 ChatGPT的答案 # 定义Transformer模块 class Transformer(nn.Module):def __init__(self, input_dim, hidden_dim, num_heads, num_layers):super(Transformer, self).__init__()self.encoder_layer nn.TransformerEncoderLayer(d_modeli…...

决定西弗吉尼亚州地区版图的关键历史事件

决定西弗吉尼亚州地区版图的关键历史事件&#xff1a; 1. 内部分裂与美国内战&#xff1a; - 在1861年美国内战爆发时&#xff0c;弗吉尼亚州作为南方邦联的一员宣布退出美利坚合众国。然而&#xff0c;弗吉尼亚州西部的一些县由于经济结构&#xff08;主要是农业非依赖奴隶制…...

LeetCode_22_中等_括号生成

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 暴力法2.2 回溯法 1. 题目 数字 n n n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a; n 3 n 3 …...

Verilog(未完待续)

Verilog教程 这个教程写的很好&#xff0c;可以多看看。本篇还没整理完。 一、Verilog简介 什么是FPGA&#xff1f;一种可通过编程来修改其逻辑功能的数字集成电路&#xff08;芯片&#xff09; 与单片机的区别&#xff1f;对单片机编程并不改变其地电路的内部结构&#xff0…...

【Linux实践室】Linux初体验

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 &#x1f514;Linux 目录结构介绍2.2 &#x1f514;Linux …...

Flutter中高级JSON处理:使用json_serializable进行深入定制

Flutter中高级JSON处理 使用json_serializable库进行深入定制 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/article/details/1363…...

华为OD技术面试案例4-2024年

个人情况&#xff1a;985本&#xff0c;目标院校非计算机专业&#xff0c;情况比较特殊&#xff0c;23年11月研究生退学&#xff0c;电子信息类专业。 初识od&#xff1a;10月底打算退学的时候在智联、BOSS上疯狂投硬件方面的岗位。投了大概一两天后有德科和HW的HR打电话给我介…...

【TestNG】(4) 重试机制与监听器的使用

在UI自动化测试用例执行过程中&#xff0c;经常会有很多不确定的因素导致用例执行失败&#xff0c;比如网络原因、环境问题等&#xff0c;所以我们有必要引入重试机制&#xff08;失败重跑&#xff09;&#xff0c;来提高测试用例成功率。 在不写代码的情况没有提供可配置方式…...

“智农”-高标准农田

高标准农田是指通过土地整治、土壤改良、水利设施、农电配套、机械化作业等措施&#xff0c;提升农田质量和生产能力&#xff0c;达到田块平整、集中连片、设施完善、节水高效、宜机作业、土壤肥沃、生态友好、抗灾能力强、与现代农业生产和经营方式相适应的旱涝保收、稳产高产…...

利用 lxml 库的XPath()方法在网页中快速查找元素

XPath() 函数是 lxml 库中 Element 对象的方法。在使用 lxml 库解析 HTML 或 XML 文档时&#xff0c;您可以通过创建 Element 对象来表示文档的元素&#xff0c;然后使用 Element 对象的 XPath() 方法来执行 XPath 表达式并选择相应的元素。 具体而言&#xff0c;XPath() 方法是…...

nginx---------------重写功能 防盗链 反向代理 (五)

一、重写功能 rewrite Nginx服务器利用 ngx_http_rewrite_module 模块解析和处理rewrite请求&#xff0c;此功能依靠 PCRE(perl compatible regular expression)&#xff0c;因此编译之前要安装PCRE库&#xff0c;rewrite是nginx服务器的重要功能之一&#xff0c;重写功能(…...

unity shaderGraph实例-物体线框显示

文章目录 本项目基于URP实现一&#xff0c;读取UV网格&#xff0c;由自定义shader实现效果优缺点效果展示模型准备整体结构各区域内容区域1区域2区域3区域4shader属性颜色属性材质属性后处理 实现二&#xff0c;直接使用纹理&#xff0c;使用默认shader实现优缺点贴图准备材质准…...

分类问题经典算法 | 二分类问题 | Logistic回归:公式推导

目录 一. Logistic回归的思想1. 分类任务思想2. Logistic回归思想 二. Logistic回归算法&#xff1a;线性可分推导 一. Logistic回归的思想 1. 分类任务思想 分类问题通常可以分为二分类&#xff0c;多分类任务&#xff1b;而对于不同的分类任务&#xff0c;训练的主要目标是…...

redis实现分布式全局唯一id

目录 一、前言二、如何通过Redis设计一个分布式全局唯一ID生成工具2.1 使用 Redis 计数器实现2.2 使用 Redis Hash结构实现 三、通过代码实现分布式全局唯一ID工具3.1 导入依赖配置3.2 配置yml文件3.3 序列化配置3.4 编写获取工具3.5 测试获取工具 四、运行结果 一、前言 在很…...

Sora引发安全新挑战

文章目录 前言一、如何看待Sora二、Sora加剧“深度伪造”忧虑三、Sora无法区分对错四、滥用导致的安全危机五、Sora面临的安全挑战总结前言 今年2月,美国人工智能巨头企业OpenAI再推行业爆款Sora,将之前ChatGPT以图文为主的生成式内容全面扩大到视频领域,引发了全球热议,这…...

Android 14.0 Launcher3定制化之桌面分页横线改成圆点显示功能实现

1.前言 在14.0的系统rom产品定制化开发中&#xff0c;在进行launcher3的定制化中&#xff0c;在双层改为单层的开发中&#xff0c;在原生的分页 是横线&#xff0c;而为了美观就采用了系统原来的另外一种分页方式&#xff0c;就是圆点比较美观&#xff0c;接下来就来分析下相关…...

SemiDrive E3 MCAL 开发系列(3)– Wdg 模块的使用

一、 概述 本文将会介绍 SemiDrive E3 MCAL Wdg 模块的基本配置&#xff0c;并且会结合实际操作的介绍&#xff0c;帮助新手快速了解并掌握这个模块的使用&#xff0c;文中的 MCAL 是基于 PTG3.0 的版本&#xff0c;开发板是官方的 E3640 网关板。 二、 Wdg 模块的主要配置 …...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

大数据学习(132)-HIve数据分析

​​​​&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

RKNN开发环境搭建2-RKNN Model Zoo 环境搭建

目录 1.简介2.环境搭建2.1 启动 docker 环境2.2 安装依赖工具2.3 下载 RKNN Model Zoo2.4 RKNN模型转化2.5编译C++1.简介 RKNN Model Zoo基于 RKNPU SDK 工具链开发, 提供了目前主流算法的部署例程. 例程包含导出RKNN模型, 使用 Python API, CAPI 推理 RKNN 模型的流程.   本…...