在看某个 golang 的教程,不太理解为啥 Init Logger 要加锁

191 天前
 theoriz
var (
	mu sync.Mutex

	// std 定义了默认的全局 Logger.
	std = NewLogger(NewOptions())
)

// Init 使用指定的选项初始化 Logger.
func Init(opts *Options) {
	mu.Lock()
	defer mu.Unlock()

	std = NewLogger(opts)
}

一般一个项目日志也就 Init 一次吧,这里加这个锁是为了什么呢 🤔 求大佬解惑

完整源码: https://github.com/marmotedu/miniblog/blob/master/internal/pkg/log/log.go

2590 次点击
所在节点    Go 编程语言
14 条回复
bv
191 天前
这代码没什么营养,不要纠结为何这么写。
yuancoder
191 天前
这只是一个普通的函数
bv
191 天前
@bv 接 #1
1. 这个函数是 Init ,不是 init
2. 锁只锁了 std = NewLogger(opts) 语句,使用时没加锁,std 变量存在 race 问题。
3. func NewLogger(opts *Options) *zapLogger 可导出方法返回了不可导出的类型,不够优雅。

初学者还是应该拜读更规范的源码,免得入门时被低质量代码熏陶,一些错误理念会根深蒂固。
kuanat
191 天前
抱着解答问题的心态点进来,看见楼上的评论就笑了。名著你可以揣测一下作者的写作思路,地摊文学就算了……

代码层面的问题 @bv 在楼上已经说得比较清楚了,我补充一点我的理解。

这种代码水平在我之前的面试标准里面,按照初级/中级/高级三档水平,只能划分到初级那一档。我只通过看链接里那个文件就做出了判断,而且对此我非常有把握。

划分到初级的主要理由是:无法准确使用接口 Interface 来实现功能解耦。这个能力在我之前负责面试的时候是中级技能里的。相比之下,代码层面反倒是小问题了。

如果看不懂的话,我可以抽时间单独就 Go 通过接口来解耦专门做个解释。有隔壁那个代码氛围的帖子在,其实不想过多评价。但我还是要说,这段代码的作者的 Go 水平真就是初学者,出来卖课就是误人子弟了。
aababc
191 天前
@bv 这里的第三点说说我的想法,大佬看看是否有问题,如果结构体需要比较复杂的初始化流程或者说在结构体初始化完成之后需要对其他的字段进行赋值,这么做应该也没有问题吧?
kele1997
191 天前
@aababc bv 说的第三点主要还是说返回了不可导出的类型吧,这种类型的变量在 go 里面感觉确实有点奇葩,其他包可以用,但是不能传递给其他函数(因为无法声明类型)。

另外复杂的结构体初始化可以用 Builder 或者 Option 模式吧
tcpdump
191 天前
啥教程?
CRVV
191 天前
加锁是对的,因为写了变量 std ,同时有两个线程写同一个变量是 data race 。
通常只会 init 一次,也不排除有人在多个线程上 init 这种情况。虽然这种用法大概率是错的,不过作为 library 让这种情况不出 data race 也是正常的。

使用的时候不需要加锁,因为同时有多个线程读同一个变量不是 data race
kneo
191 天前
正确与否看具体的业务逻辑。一般来说这段代码如果真被并发调用,哪怕加了锁还是会初始化两次,不是典型的做法。如果希望初始化一次尽量使用 sync.Once 。
cabing
191 天前
为啥不用 once...

看了错误的代码示范了吧。。
aababc
191 天前
@kele1997 确实,我一直想着依赖接口,忽略了直接使用类型的情况。
changz
191 天前
我猜估计是 go race 的时候看到问题了,又懒得改
tbxark
191 天前
@bv 确实他应该下面这么写,我猜他这个 std 应该是想保留具体类型而不是接口类型

```go
var _ Logger = &zapLogger{}

var (
mu sync.Mutex
std = newLogger(NewOptions())
)

func Init(opts *Options) {
mu.Lock()
defer mu.Unlock()
std = newLogger(opts)
}

func NewLogger(opts *Options) Logger {
return newLogger(opts)
}

func newLogger(opts *Options) *zapLogger {
```
lveye
190 天前
优雅一点的方式是使用 sync.Once (内部机制也是使用了锁),比如这样:
```var (
stdOnce sync.Once

// std 定义了默认的全局 Logger.
std = NewLogger(NewOptions())
)

// Init 使用指定的选项初始化 Logger.
func Init(opts *Options) {
stdOnce.Do(func() {
std = NewLogger(opts)
})
}
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1038429

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX