Table of Contents generated with DocToc
- 多个
defer
的执行顺序为"后进先出" defer
,return
,返回值三者的执行逻辑应该是:return
最先执行,return
负责将结果写入返回值中;接着defer
开始一些收尾工作;最后函数携带当前返回值退出.
如果函数的返回值是无名的(不带命名返回值),则go语言会在执行return的时候执行一个类似创建临时变量作为return值的动作,而有名返回值的函数,由于返回值在函数定义的时候已经将该变量进行定义,在执行return的时候会先执行返回值保存工作,而后续的defer函数会改变这个返回值(虽然defer是在return之后执行的,,但是由于使用的函数定义的变量,所以执行defer操作后对该变量的修改会影响到return的值)。
1. 进程、线程、协程
进程:资源分配和CPU调度的基本单位
线程:CPU调度的基本单位,线程除了有一些自己的必要的堆栈空间之外,其它的资源都是共享的线程中的,共享的资源包括:
rune是用来区分字符串和整数值的
- byte等同于int8,即一个字节长度,常用来处理ascii字符
- rune等同于int32,即4个字节(2个字符)的长度,常用来处理unicode或utf8字符
什么是内存逃逸?
内存逃逸(Memory Escape) 是指一个原本应该分配在栈(stack)上的变量,由于某些原因被分配到了堆(heap)上。这种现象在 Go 的垃圾回收机制中是允许的,但在某些情况下可能会导致不必要的堆分配,从而影响性能。
以下是 Python 进程、线程、协程与 Go Goroutine 的核心区别总结,从调度方式、资源消耗、适用场景、性能表现等维度综合对比:
对比表格
特性 | Python 进程 | Python 线程 | Python 协程 | Go Goroutine |
---|---|---|---|---|
调度方式 | 操作系统内核调度 | 操作系统线程调度(受 GIL 限制) | 用户态事件循环调度 | Go 运行时调度(无 GIL) |
资源占用 | 高(独立内存,MB/进程) | 中(MB/线程,共享内存) | 极低(KB/协程) | 极低(2KB/Goroutine) |
并发能力 | 多核并行(进程数≈CPU 核数) | 单核并发(GIL 限制,无法并行) | 单线程数万协程(I/O 密集型) | 百万级 Goroutine(多核并行) |
切换成本 | 高(内核切换) | 高(内核切换 + GIL 竞争) | 极低(用户态切换) | 极低(用户态切换) |
适用场景 | CPU 密集型任务(如计算) | 简单 I/O 任务(非高并发) | 高并发 I/O(如 HTTP、RPC) | 高并发 I/O + CPU 混合任务 |
并行能力 | 多核并行 | 单核伪并行(受 GIL 限制) | 单线程异步(需多进程配合) | 多核并行(默认使用所有 CPU) |
通信机制 | 进程间通信(IPC,如管道、队列) | 线程锁(Lock、RLock) | 异步队列(asyncio.Queue ) |
Channel(CSP 模型,无锁通信) |
代码复杂度 | 高(需处理进程间同步) | 中(需处理线程锁和 GIL) | 中(需理解异步编程模型) | 低(Channel 简化并发逻辑) |
生态支持 | 成熟(multiprocessing ) |
成熟但受限(GIL) | 依赖异步库(如 aiohttp ) |
原生支持(标准库全异步化) |
典型应用 | 科学计算、数据处理 | 简单并发任务(文件读写) | Web 服务、爬虫、微服务 | 高并发后端、分布式系统 |
核心区别详解
1. 并行与并发能力
- Python 进程:唯一绕过 GIL 的方式,适合多核 CPU 并行计算,但进程间通信(IPC)成本高。
- Python 线程:受 GIL 限制,单核并发,适合简单 I/O 任务(如文件读写),但不适合高并发网络请求。
- Python 协程:单线程内高并发 I/O(如处理 10k+ HTTP 连接),但无法并行利用多核。
- Go Goroutine:多核并行 + 高并发,Goroutine 由 Go 运行时自动调度到多个 CPU 核心,无 GIL 限制。
2. 资源与性能
-
资源消耗:
原文:go中内存泄露的发现与排查 - ZhanLi - 博客园 (cnblogs.com)
搬运的原文如下
内存泄露
前言
go中的内存泄露一般都是goroutine泄露,就是goroutine没有被关闭,或者没有添加超时控制,让goroutine一只处于阻塞状态,不能被GC。
内存泄露发生的可能情况
简单归纳一下,还是“临时性”内存泄露和“永久性”内存泄露:
临时性泄露,指的是该释放的内存资源没有及时释放,对应的内存资源仍然有机会在更晚些时候被释放,即便如此在内存资源紧张情况下,也会是个问题。这类主要是string、slice底层buffer的错误共享,导致无用数据对象无法及时释放,或者defer函数导致的资源没有及时释放。 永久性泄露,指的是在进程后续生命周期内,泄露的内存都没有机会回收,如goroutine内部预期之外的 for-loop 或者 chan select-case 导致的无法退出的情况,导致协程栈及引用内存永久泄露问题。
原文参考:Go语言的垃圾回收机制,图文并茂 一篇搞懂!_go垃圾回收-CSDN博客
Go垃圾回收
标记清除法
Go 1.5之前使用的垃圾回收策略是标记清除法
根对象就是应用程序中可以直接或间接访问的对象 根对象主要包括:
- 全局变量:在程序编译期间就能确定,全局变量存在于程序的整个生命周期
- 执行栈:Go语言中协程是分配在堆上,每个协程都含有自己的执行栈
- 寄存器:寄存器的值可能表示一个指针,这些指针可能指向某些赋值器分配的堆内存区块
简单来说,标记清除算法整个过程就是把从根对象遍历不到的对象清除掉,根对象就是垃圾收集器判断哪些对象是活动对象(还在使用)的起点
Raft是一种为简化分布式系统共识而设计的算法,其核心思想是将复杂问题分解为领导选举、日志复制和安全性三个关键部分。以下是对Raft的详细介绍:
1. 核心概念
- 节点角色:
- 领导者(Leader):处理客户端请求,管理日志复制。
- 跟随者(Follower):被动响应领导者和候选者的请求。
- 候选者(Candidate):在选举期间临时角色,发起投票请求。
- 任期(Term):
- 全局递增的编号,唯一标识领导者的任期。
- 用于检测过时的请求(如旧领导者的消息)。
2. 领导选举
- 触发条件:跟随者超时未收到心跳(选举超时,通常随机化为150-300ms),转为候选者。
- 选举过程:
- 候选者自增任期,向所有节点发送RequestVote请求。
- 节点仅对首个符合条件的请求投票(同一任期内一票一投,且候选者日志足够新)。
- 候选者获多数票即成为新领导者,立即发送心跳确立权威。
- 安全性:通过日志完整性检查(选举限制)避免选出过时的领导者。
3. 日志复制
- 流程:
- 客户端请求由领导者转化为日志条目。
- 并行复制到所有跟随者。
- 当多数节点确认后,领导者提交条目并应用到状态机。
- 通知跟随者提交日志。
- 一致性保证:
- 每个日志条目包含任期号和索引。
- 日志匹配特性:若两节点日志中某索引的条目任期相同,则之前的所有条目均一致。
- 领导者处理不一致时,通过强制覆盖跟随者的日志确保最终一致。
4. 安全性机制
- 提交规则:领导者只能提交当前任期的日志(间接提交旧任期的条目)。
- 崩溃恢复:
- 节点重启后根据持久化的日志恢复状态。
- 新领导者通过日志比对修复不一致的跟随者日志。
- 网络分区处理:高任期的新领导者优先,旧领导者因低任期自动降级。
5. 优化与扩展
- 日志压缩与快照:
- 定期生成快照以压缩日志,减少存储和传输开销。
- 跟随者落后时,领导者直接发送快照加速同步。
- 线性一致性:通过领导者序列化所有写请求,确保操作顺序一致性。
6. Raft vs. Paxos
- 易用性:Raft通过强领导者和连续日志简化实现;Paxos灵活但复杂。
- 日志结构:Raft日志无空洞,Paxos允许空洞但最终填补。
- 领导权:Raft仅一个活跃领导者,Paxos允许多提议者共存。
7. 实现要点
- 持久化状态:保存当前任期、投票记录和日志,防止重启后状态丢失。
- 定时器管理:合理设置心跳间隔与随机化选举超时,避免活锁。
- 测试验证:模拟网络分区、节点宕机等故障,确保算法正确性。
8. 应用场景
适用于需强一致性的分布式系统,如分布式数据库(etcd、CockroachDB)、协调服务(Consul)等。
参考:https://www.jianshu.com/p/7c4fb2c3c66e
Go语言版本
|
|
sync.map 则是一种并发安全的 map,在 Go 1.9 引入
1 特点
Map 类型针对两种常见用例进行了优化:
好的,以下是一个完整的方案,包括数据库设计、GORM 模型、用户注册和激活功能的实现,以及通过邮件发送激活码的过程。
GORM 模型
|
|
数据库设计
users 表
|
|
subscriptions 表
|
|
resources 表
|
|
user_resources 表
|
|
activations 表
|
|
详细步骤
-
数据库设计:
设计一个系统,提供不同的资源访问权限给免费用户和 VIP 用户,这里将分别介绍下整体设计思路和数据库设计,并用 GORM 的方式说明。
整体设计思路
- 用户注册和登录模块。
- 资源模块,可以是文件、视频、API 调用等。
- 订阅模块,有免费和 VIP 两种订阅。
- 权限控制模块,根据用户订阅类型控制资源访问权限。
数据库设计
我们需要设计几个主要的数据表:
全文复制保存方便自己查看。
1、Protobuf编码原理介绍
序列化算法被广泛应用于各种通信协议中,本文对序列化算法进行狭义定义:
将某个struct或class的内存数据和通信数据链路上的字节流进行互相转化的算法。
从https://mp.weixin.qq.com/s/OIHqmgK4V7Y26uYoFjsCyA 复制
Golang 实现SOLID 设计原则
本章节按照设计模式中的核心设计原则介绍在Go语言中的实现。
非原创
好文:https://yizhi.ren/2019/06/03/goscheduler
参考:https://blog.csdn.net/xmcy001122/article/details/119392934
go server以相当优雅的姿势退出(待继续完善)
|
|
CAS算法(compare and swap)
CAS算法是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数:
go中实现超时控制下执行函数功能
|
|
删除具有级联关系的数据
参考:https://gorm.io/zh_CN/docs/associations.html#Association-Mode
带 Select 的删除
你可以在删除记录时通过 Select 来删除具有 has one、has many、many2many 关系的记录,例如:
1. 设计理念
-
Go:
- 强调静态类型、编译速度和并发编程。
- 语法简单,几乎没有语法糖,强制代码风格统一(如
gofmt
)。
-
Python:
- 强调动态类型、灵活性和开发效率。
- 语法简洁,支持多种编程范式(面向对象、函数式、过程式)。
2. 类型系统
- Go:
- 静态类型语言,变量类型在编译时确定。
- 类型安全,编译时检查类型错误。
- 不支持泛型(Go 1.18 引入了泛型,但使用受限)。
- Python:
- 动态类型语言,变量类型在运行时确定。
- 类型灵活,但运行时可能出现类型错误。
- 支持泛型(通过类型注解和第三方库实现)。
3. 性能
- Go:
- 编译为机器码,执行速度快。
- 内存管理高效,垃圾回收机制优化良好。
- 适合高并发、高性能场景(如网络服务、微服务)。
- Python:
- 解释执行,执行速度较慢。
- 内存管理依赖垃圾回收,性能较低。
- 适合开发效率优先的场景(如脚本、数据分析、原型开发)。
4. 并发模型
- Go:
- 内置 goroutine 和 channel,支持轻量级并发。
- goroutine 是用户级线程,开销小,适合高并发场景。
- channel 用于 goroutine 之间的通信,避免共享内存的问题。
- Python:
- 支持多线程、多进程和异步编程(如
asyncio
)。 - 由于全局解释器锁(GIL),多线程无法充分利用多核 CPU。
- 多进程和异步编程是常见的并发解决方案。
- 支持多线程、多进程和异步编程(如
5. 语法
- Go:
- 语法简洁,强制代码风格统一。
- 没有类和继承,通过接口和组合实现面向对象。
- 错误处理通过返回值显式处理(如
err != nil
)。
- Python:
- 语法灵活,支持丰富的语法糖。
- 支持类和继承,面向对象特性完善。
- 错误处理通过异常机制(
try-except
)。
6. 生态系统
- Go:
- 标准库功能强大,适合网络编程和系统编程。
- 第三方库数量较少,但质量较高。
- 工具链完善(如
go build
、go test
、go mod
)。
- Python:
- 标准库功能丰富,涵盖多种领域。
- 第三方库数量庞大,覆盖广泛的应用场景(如 Web 开发、数据分析、机器学习)。
- 包管理工具多样(如
pip
、conda
)。
7. 应用场景
- Go:
- 网络服务(如微服务、API 服务)。
- 系统编程(如命令行工具、操作系统组件)。
- 高并发、高性能场景(如分布式系统、消息队列)。
- Python:
- 数据分析与科学计算(如 Pandas、NumPy)。
- 机器学习与人工智能(如 TensorFlow、PyTorch)。
- Web 开发(如 Django、Flask)。
- 脚本与自动化任务。
8. 开发效率
- Go:
- 编译速度快,适合大型项目。
- 代码简洁,但开发效率可能不如 Python。
- Python:
- 开发效率高,适合快速原型开发。
- 代码易读易写,但性能较低。
Go Signal信号处理
参考: [Go Signal信号处理]((17条消息) Go Signal信号处理_无风的雨-CSDN博客_go signal)
可以先参考官方资料[go.signal](signal package - os/signal - pkg.go.dev)
1.占位符分别代表了什么?
golang 的fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf。 定义示例类型和变量
|
|
2 普通占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%v | 相应值的默认格式。 | Printf("%v", people) | {zhangsan} |
%+v | 打印结构体时,会添加字段名 | Printf("%+v", people) | {Name:zhangsan} |
%#v | 相应值的Go语法表示 | Printf("#v", people) | main.Human{Name:“zhangsan”} |
%T | 相应值的类型的Go语法表示 | Printf("%T", people) | main.Human |
%% | 字面上的百分号,并非值的占位符 | Printf("%%") | % |
3 布尔占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%t | true或false | Printf("%t", true) | true |
4 整数占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 二进制表示 | Printf("%b", 5) | 101 |
%c | 相应Unicode码点锁表示的字符 | Printf("%c", 0x4e2d) | 中 |
%d | 十进制表示 | Printf("%d", 0x12) | 18 |
%o | 八进制表示 | Printf("%d", 10) | 12 |
%q单引号围绕的字符字面值,由Go语法安全地转义 | Printf("%q", 0x4E2D) | ‘中’ | |
%x | 十六进制表示,字母形式为小写 a-f | Printf("%x", 13) | d |
%X | 十六进制表示,字母形式为大写 A-F | Printf("%x", 13) | D |
%U | Unicode格式:U+1234,等同于 “U+%04X” | Printf("%U", 0x4E2D) | U+4E2D |
5 浮点数和复数的组成部分(实部和虚部)
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 无小数部分的,指数为二的幂的科学计数法, 与 strconv.FormatFloat 的 ‘b’ 转换格式一致。例如 -123456p-78 | ||
%e | 科学计数法,例如 -1234.456e+78 | Printf("%e", 10.2) | 1.020000e+01 |
%E | 科学计数法,例如 -1234.456E+78 | Printf("%e", 10.2) | 1.020000E+01 |
%f | 有小数点而无指数,例如 123.456 | Printf("%e", 10.2) | 10.200000 |
%g | 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 | Printf("%g", 10.20) | 10.2 |
%G | 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 | Printf("%G", 10.20+2i) | (10.2+2i) |
6 字符串与字节切片
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%s | 输出字符串表示(string类型或[]byte) | Printf("%s", []byte(“Go语言”)) | Go语言 |
%q | 双引号围绕的字符串,由Go语法安全地转义 | Printf("%q", “Go语言”) | “Go语言” |
%x | 十六进制,小写字母,每字节两个字符 | Printf("%x", “golang”) | 676f6c616e67 |
%X | 十六进制,大写字母,每字节两个字符 | Printf("%X", “golang”) | 676F6C616E67 |
7 指针
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%p | 十六进制表示,前缀 0x | Printf("%p", &people) | 0x4f57f0 |
8 其它标记
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
+ | 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。 | Printf("%+q", “中文”) | “\u4e2d\u6587” |
- | 在右侧而非左侧填充空格(左对齐该区域) | ||
# | 备用格式:为八进制添加前导 0(%#o) 为十六进制添加前导 0x(%#x)或 0X(%#X),为 %p(%#p)去掉前导 0x; 如果可能的话,%q(%#q)会打印原始(即反引号围绕的)字符串; 如果是可打印字符,%U(%#U)会写出该字符的 Unicode 编码形式(如字符 x 会被打印成 U+0078 ‘x’)。 | Printf("%#U", ‘中’) | U+4E2D |
’ ' | (空格)为数值中省略的正负号留出空白(% d); 以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开 | ||
0 | 填充前导的0而非空格;对于数字,这会将填充移到正负号之后 |
互斥锁
用一个互斥锁来在Go协程间安全的访问数据
|
|
尽量减少锁的持有时间
- 细化锁的粒度。通过细化锁的粒度来减少锁的持有时间以及避免在持有锁操作的时候做各种耗时的操作。
- 不要在持有锁的时候做 IO 操作。尽量只通过持有锁来保护 IO 操作需要的资源而不是 IO 操作本身:
|
|
善用 defer 来确保在函数内正确释放
通过 defer 可以确保不会遗漏释放锁操作,避免出现死锁问题,以及避免函数内非预期的 panic 导致死锁的问题 不过使用 defer 的时候也要注意别因为习惯性的 defer m.Unlock() 导致无意中在持有锁的时候做了 IO 操作,出现了非预期的持有锁时间太长的问题。
我需要完成一项任务,但是这项任务需要满足一定条件才可以执行,否则我就等着。
那我可以怎么获取这个条件呢?一种是循环去获取,一种是条件满足的时候通知我就可以了。显然第二种效率高很多。
通知的方式的话,golang里面通知可以用channel的方式
Go 程序从 main 包的 main()
函数开始,在程序启动时,Go 程序就会为 main()
函数创建一个默认的 goroutine
。
所有 goroutine
在 main()
函数结束时会一同结束。
若在启用的goroutine
中不使用WaitGroup
的话会因为main函数已执行完,阻塞的函数与发送信号的函数会一同结束,不能真正实现阻塞的功能。
1. 首先声明两个基础结构体(其他语言的基类吧:))
|
|
并给Animal
类增加一个方法Walk()