代码编译过程

  1. 全局变量会进行全局初始化

    1
    2
    
    type noticeRepository struct{}
    var Notice = &noticeRepository{}
    
  2. model 层为何不全局实例化?因为 model 层的结构体带有属性,如果全局初始化则 model 层结构体实例的属性可能会出现重复,导致数据异常!

    示例:用户 A 获取了有数据的用户结构体,则会覆盖初始的 model 结构体,接着用户 B 查询不到任何数据时,则会拿到用户 A 的用户结构体数据,而不是空结构体。

扇出和扇入

通常情况下多个函数可以同时从一个channel接收数据,直到channel关闭,这种情况被称作扇出。这是一种将工作分布给一组工作者的方法,目的是并行使用CPU和I/O。

如果一个函数同时接收并处理多个channel输入并转化为一个输出channel,直到所有的输入channel都关闭后,关闭输出channel,这种情况就被称作扇入。

相关文章链接

死锁、活锁和饥饿

死锁

死锁程序是所有并发进程 彼此等待 的程序。在这种情况下,如果没有外界的干预,这个程序将永远无陆恢复。

活锁

活锁是正在 主动执行 并发操作的程序,但是这些操作无战向前推进程序的状态。

你曾经在走廊走向另一个人吗?她移动到一边让你通过,但你也做了同样的事情。所以你转到另一边,但她也是这样做的。想象一下这个情形永远持续下去,你就明白了活锁。

饥饿

饥饿是在任何情况下,并发进程都无法获得执行工作所需的所有资源。

饥饿通常意味着有一个或多个贪婪的并发进程,它们不公平地阻止一个或多个井发进程,以尽可能有效地完成工作,或者阻止全部并发进程。

引入私有仓库报错的解决方案

  1. 配置 git 全局变量

    1
    2
    
    git config --global url.ssh://git@gitlab.xxx.com/.insteadof https://gitlab.xxx.com/
    git config --global url.https://gitlab.xxx.com/.insteadof http://gitlab.xxx.com/
    
  2. 全局生效:go env -w GOPRIVATE=gitlab.xxx.com [或当前终端生效:export GOPRIVATE=gitlab.xxx.com]

  3. go get gitlab.xxx.com/xxxx@v0.0.1

  4. go mod tidy && go mod vendor

go.mod 引入本地包

1
2
require "xxx/p" v0.0.0
replace "xxx/p" => "../p2"

跳出多重循环的高级 break 用法说明

在循环外使用 label 标记定义退出哪一层循环。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
loop:
for {
 select{
 case <-tick.C:
  //do someting
 case <- stop:
  break loop // 直接跳到 loop 标签位置,相当于退出 select 循环和 for 循环
 }
}
doNext()

golang ast初探:一个懒人源码分析工具

查看原文

一个懒人源码分析工具

throw

其实 go 语言源码中一些地方有一些 throw 调用,这个函数会打印相应的 fatal msg,并退出整个程序,因为这类报错被 go 语言认为无法动态修复的崩溃。所以这类奔溃与 panic 不同,属于无法通过 defer 和 recover 捕获的崩溃(因为无法修复)。

map:熟悉 go 语言的开发者都知道,在 go 多协程架构使用便利的情况下,往往存在很多线程不安全的变量,map 就是其中最经典的栗子,当在并发下,在没有添加读写锁的情况下对 map 进行写、读写操作时,也会抛出 throw 崩溃。

需要注意的是,这类崩溃是直接 down 掉整个进程的,所以我们线上使用 go 语言进行应用开发时,一定要记得使用 supervisor 之类的进程管理工具,确保进行崩溃后先拉起来,再进行修复。否则会产生大面积机器完全宕机的情况。

再需要注意的一点是,go 语言中一个进程往往有很多 goroutinue 在同时进行,如果发生 throw 奔溃时,整个进程都会被关掉,如果通过日志,会发现打印了无数堆栈信息的日志(所有 goroutinue 的日志),这时候千万不要在堆栈日志上下功夫了,因为打印出来的都是正常日志,只需要查看日志中的 fatal 关键字即可找出真正的问题所在。

1
2
3
4
5
6
7
8
9
testmap := make(map[int]int)
for i := 0; i < 10; i++ {
    go func() {
        for true{
            testmap[1] = 10 // fatal: sync: curcurent map writes
        }
    }()
}
time.Sleep(time.Second*5)

uinptr && unsafe.Pointer

两者都是数据类型。

uintptr用来进行指针计算,因为它是整型,所以很容易计算出下一个指针所指向的位置。

unsafe.Pointer是特别定义的一种指针类型,它可以包含任意类型变量的地址。

静态代码分析工具

pre-commit hook

在项目开发中,我们都会使用到 git,因此我们可以将代码静态检查放在一个 git 触发点上,而不用每次写完代码手动去执行 golangci-lint run 等命令。这里,我们就需要用到 git hooks。

git hooks 是 git 的一种钩子机制,可以让用户在 git 操作的各个阶段执行自定义的逻辑。git hooks 在项目根目录的 .git/hooks 下面配置,配置文件的名称是固定的,实质上就是一个个 shell 脚本。根据 git 执行体,钩子被分为客户端钩子和服务端钩子两类。

客户端钩子包括:pre-commit、prepare-commit-msg、commit-msg、post-commit 等,主要用于控制客户端 git 的提交工作流。服务端钩子:pre-receive、post-receive、update,主要在服务端接收提交对象时、推送到服务器之前调用。

使用go自带的vet工具,是比较简单的工具,无法递归检查整个服务的代码

1
2
3
4
5
6
7
8
# 分析单个文件或多个文件
go vet xxx.go xx2.go

# 一个包下所有源文件进行检测
go tool vet source/directory/*.go

# 利用vet对一个package进行检查,当然传入的包名必须是 相对路径 或者完整package
go vet ./source/repository

使用 golangci-lint

golangci-lint 代码检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# install
go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# golangci-lint 可以通过 -E/--enable 去开启指定 linter,或者 -D/--disable 禁止指定 linter。
golangci-lint run --disable-all -E errcheck

# 分析对整个服务代码
golangci-lint run <=> golangci-lint run ./...

# 对某特定的文件或文件夹进行分析
golangci-lint run dir1 dir2/... dir3/file1.go