Go语言小细节

简介: Go语言小细节

Go语言小细节

结构体

  • 结构体中允许存在匿名字段,即只有类型没有具体的变量名,但是一个结构体内只允许有一个相同类型的
  • 结构体中字段大写开头表示可公开访问,小写表示私有(仅在当前结构体的包中可访问)
  • 在编写结构体的 Tag 时,不要在key和value中添加空格,容错能力很差
//Student 学生
type Student struct {
  ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key
  Gender string //json序列化是默认使用字段名作为key
  name   string //私有不能被json包访问
}
  • 在结构体的方法中,如果要修改 slice、array、map 这些成员属性,要注意,因为他们的指向都是地址, GO 中函数的参数虽然是一份拷贝,但是拷贝的仍然是一份地址,如果你在方法中不进行 copy,你在外部修改源数据也会影响成员的内部

package包

一个包的初始化过程是按照代码中引入的顺序来进行的,所有在该包中声明的 init 函数将被串行调用并且仅调用一次。每一个包的初始化的时候都是先执行依赖包中的声明的 init 函数再执行当前包中的 init 函数,确保在程序 main 函数开始执行时所有的依赖包都已经初始化完成。

接口

  • 一般在接口命名时会 + er 作为单词的结尾
  • 在实现接口的过程中,如果是值接收者,接口类型的变量时可以被 结构体的实例化地址或者对象变量赋值的,但如果是指针接收者,那么不能将非 结构体的引用赋值给接口变量
  • 接口也可以和结构体一样进行组合,只要实现新结构体中的所有方法,即认为它实现了这个新的接口
  • 空接口可以不必使用 type 关键字声明,空接口可以存储任何类型的值
  • 只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。切记不要为了使用接口类型而增加不必要的抽象,导致不必要的运行时损耗。

并发

  • 串行:无法同步,只有完成了上一步才能执行下一步
  • 并行:同一时刻执行多个任务
  • 并发:同一个时间段执行多个任务
  • 进程:程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
  • 线程:操作系统基于进程开启的轻量级进程,是操作系统调度执行的最小单元
  • 协程:非操作系统提供而是由用户自行创建和控制的用户态"线程",比线程更轻量级

并发模型

  • 线程&锁模型
  • Actor模型
  • CSP 模型
  • Fork&Join模型

go的并发主要是通过基于 CSP 的 goroutine 和 channel 来实现,当然也支持使用传统的多线程共享内存的并发方式

一个 goroutine 会以一个很小的栈开始它的生命周期,一般只需要 2kb。区别于操作系统线程由系统内核进行调度,goroutine 是由 Go运行时(runtime)负责调度。例如 Go 运行时会只能的将 M 个 goroutine 合理地分配给 N 个操作系统线程,实现类型 m:n 的调度机制,不再需要 Go 开发者自行在代码层面维护一个线程池。

goroutine 的调度是Go语言运行时(runtime)层面的实现,是完全由 Go 语言本身实现的一套调度系统——go scheduler。它的作用是按照一定的规则将所有的 goroutine 调度到操作系统线程上执行,目前 Go 采用的调度策略是 GMP 模型。

  • main goroutine如果执行完了,其他的 goroutine 没有执行完,也会被关闭,所以我们会使用 WaitGroup 等待协程的完成
  • 多个 goroutine 的调度是随机的

动态栈

操作系统的线程一般都有固定的栈内存(通常为2MB),而 Go 语言中的 goroutine 非常轻量级,一个 goroutine 的初始栈空间很小(一般为2KB),所以在 Go 语言中一次创建数万个 goroutine 也是可能的。并且 goroutine 的栈不是固定的,可以根据需要动态地增大或缩小, Go 的 runtime 会自动为 goroutine 分配合适的栈空间。

goroutine调度

单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的, goroutine 则是由Go运行时(runtime)自己的调度器调度的,完全是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身 goroutine 的超轻量级,以上种种特性保证了 goroutine 调度方面的性能。

GOMAXPROCS

Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个 OS 线程来同时执行 Go 代码。默认值是机器上的 CPU 核心数。例如在一个 8 核心的机器上,GOMAXPROCS 默认为 8。Go语言中可以通过runtime.GOMAXPROCS函数设置当前程序并发时占用的 CPU逻辑核心数。(Go1.5版本之前,默认使用的是单核心执行。Go1.5 版本之后,默认使用全部的CPU 逻辑核心数。)

channel

单纯地将函数并发执行是没有意义的。函数与函数间交换数据才能体现并发执行函数的意义。

虽然可以使用共享内存进行数据交换,但是共享内存在不同的 goroutine 中容易发生竞态问题。为了保证数据交换的正确性,很多并发模型中必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

如果说 goroutine 是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。

Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明 channel 的时候需要为其指定元素类型。

注意:一个通道值是可以被垃圾回收掉的。通道通常由发送方执行关闭操作,并且只有在接收方明确等待通道关闭的信号时才需要执行关闭操作。它和关闭文件不一样,通常在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

关闭后的通道有以下特点:

  • 对一个关闭的通道再发送值就会导致 panic。
  • 对一个关闭的通道进行接收会一直获取值直到通道为空。
  • 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
  • 关闭一个已经关闭的通道会导致 panic。

无缓存通道

  • 它需要等待接收后才不会造成死锁,如果没有人接收就会 deadlock
  • 使用无缓冲通道进行通信将导致发送和接收的 goroutine 同步化。因此,无缓冲通道也被称为同步通道。

有缓存通道

只要通道的容量大于零,那么该通道就属于有缓冲的通道,通道的容量表示通道中最大能存放的元素数量。当通道内已有元素数达到最大容量后,再向通道执行发送操作就会阻塞,除非有从通道执行接收操作。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。

我们可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量,虽然我们很少会这么做。

单向通道

<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收

Select多路复用

  • 可处理一个或多个 channel 的发送/接收操作。
  • 如果多个 case 同时满足,select 会随机选择一个执行。
  • 对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出。

并发安全和锁

有时候我们的代码中可能会存在多个 goroutine 同时操作一个资源(临界区)的情况,这种情况下就会发生竞态问题(数据竞态)。这就好比现实生活中十字路口被各个方向的汽车竞争,还有火车上的卫生间被车厢里的人竞争。

互斥锁是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个 goroutine 可以访问共享资源。Go 语言中使用sync包中提供的Mutex类型来实现互斥锁。

但是一般在我们的应用中都是读多写少,我们会考虑使用读写互斥锁,当我们并发的去读取一个资源而不涉及资源修改的时候是没有必要加互斥锁的

读写锁分为两种:读锁和写锁。当一个 goroutine 获取到读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待;而当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待。

sync.WaitGroup

sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了 N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用 Done 方法将计数器减1。通过调用 Wait 来等待并发任务执行完,当计数器值为 0 时,表示所有并发任务已经完成。

sync.Once

在某些场景下我们需要确保某些操作即使在高并发的场景下也只会被执行一次,例如只加载一次配置文件等。

Go语言中的sync包中提供了一个针对只执行一次场景的解决方案——sync.Once,sync.Once只有一个Do方法,其签名如下:

sync.Map

Go 语言中内置的 map 不是并发安全的

原子操作 atomic

atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用,使用通道或者 sync 包的函数/类型实现同步更好。

struct转换成map时会出现数据

类型转换错误的问题

// UserInfo 用户信息
type UserInfo struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
}
u1 := UserInfo{Name: "q1mi", Age: 18}
这个如果 u1 转成 map 的话,先 json,然后序列化回来给 map, 这时这个 Age 就变成了 float 类型
  • 方法一:这里使用反射遍历结构体字段的方式生成map
  • 第三方库structs


目录
相关文章
|
5天前
|
安全 Go 调度
Go语言中的并发编程
Go语言自带了强大的并发编程能力,它的协程机制可以让程序轻松地实现高并发。本文将从并发编程的基础概念出发,介绍Go语言中的协程机制、通道和锁等相关知识点,帮助读者更好地理解并发编程在Go语言中的实践应用。
|
1天前
|
存储 算法 Go
go语言中的延迟执行函数
【5月更文挑战第13天】`defer`是Go语言中用于延迟执行函数的关键字,尤其适用于资源管理,如文件关闭和锁的释放。它在函数返回前按照LIFO顺序执行,确保资源在任何返回路径下都能正确释放。`defer`可以拦截`panic`并在函数返回前执行,但无法阻止某些致命的`panic`。此外,`defer`可用于修改返回值、输出调试信息和还原变量值。尽管在某些场景下可能影响性能,但Go的优化使得其在多数情况下性能表现良好,特别是在资源清理方面。在Go 1.20及以后的版本,`defer`的性能已显著提升,尤其是在高计算量的场景下。
210 2
|
3天前
|
数据采集 Web App开发 Go
Go语言与chromedp结合:实现Instagram视频抓取的完整流程
使用Go语言和chromedp库,本文展示了如何抓取Instagram的视频文件,同时通过代理IP保障爬虫稳定和隐私。步骤包括安装chromedp、配置代理(如亿牛云),创建Chrome会话,导航至Instagram,提取视频URL,然后下载视频。关键操作有设置代理服务器、启动Chrome会话、抓取和下载视频。提供的代码示例详细解释了实现过程,有助于开发者学习Instagram数据采集。
Go语言与chromedp结合:实现Instagram视频抓取的完整流程
|
3天前
|
缓存 Go 调度
浅谈在go语言中的锁
【5月更文挑战第11天】本文评估了Go标准库`sync`中的`Mutex`和`RWMutex`性能。`Mutex`包含状态`state`和信号量`sema`,不应复制已使用的实例。`Mutex`适用于保护数据,而`RWMutex`在高并发读取场景下更优。测试显示,小并发时`Mutex`性能较好,但随着并发增加,其性能下降;`RWMutex`的读性能稳定,写性能在高并发时低于`Mutex`。
136 0
浅谈在go语言中的锁
|
4天前
|
存储 安全 编译器
go语言中进行不安全的类型操作
【5月更文挑战第10天】Go语言中的`unsafe`包提供了一种不安全但强大的方式来处理类型转换和底层内存操作。包含两个文档用途的类型和八个函数,本文也比较了不同变量和结构体的大小与对齐系数,强调了字段顺序对内存分配的影响。
88 8
go语言中进行不安全的类型操作
|
4天前
|
Go
配置go语言下载包 - 蓝易云
这个命令会将包下载到你的GOPATH目录下,并自动安装它。
66 1
|
6天前
|
Ubuntu Unix Linux
【GO基础】1. Go语言环境搭建
【GO基础】1. Go语言环境搭建
|
7天前
|
JSON 前端开发 Go
lucky - go 语言实现的快速开发平台
go 语言实现的快速开发平台,自动生成crud代码,前端页面通过json配置,无需编写前端代码。
14 0
|
8天前
|
存储 Java Go
Go 语言切片如何扩容?(全面解析原理和过程)
Go 语言切片如何扩容?(全面解析原理和过程)
19 2
|
9天前
|
负载均衡 Go 调度
使用Go语言构建高性能的Web服务器:协程与Channel的深度解析
在追求高性能Web服务的今天,Go语言以其强大的并发性能和简洁的语法赢得了开发者的青睐。本文将深入探讨Go语言在构建高性能Web服务器方面的应用,特别是协程(goroutine)和通道(channel)这两个核心概念。我们将通过示例代码,展示如何利用协程处理并发请求,并通过通道实现协程间的通信和同步,从而构建出高效、稳定的Web服务器。