V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
theoriz
V2EX  ›  Go 编程语言

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

  •  
  •   theoriz · 12 天前 · 1987 次点击
    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

    14 条回复    2024-05-08 21:15:58 +08:00
    bv
        1
    bv  
       12 天前
    这代码没什么营养,不要纠结为何这么写。
    yuancoder
        2
    yuancoder  
       12 天前
    这只是一个普通的函数
    bv
        3
    bv  
       12 天前   ❤️ 1
    @bv 接 #1
    1. 这个函数是 Init ,不是 init
    2. 锁只锁了 std = NewLogger(opts) 语句,使用时没加锁,std 变量存在 race 问题。
    3. func NewLogger(opts *Options) *zapLogger 可导出方法返回了不可导出的类型,不够优雅。

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

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

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

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

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

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

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

    看了错误的代码示范了吧。。
    aababc
        11
    aababc  
       11 天前
    @kele1997 确实,我一直想着依赖接口,忽略了直接使用类型的情况。
    changz
        12
    changz  
       11 天前
    我猜估计是 go race 的时候看到问题了,又懒得改
    tbxark
        13
    tbxark  
       11 天前
    @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
        14
    lveye  
       10 天前
    优雅一点的方式是使用 sync.Once (内部机制也是使用了锁),比如这样:
    ```var (
    stdOnce sync.Once

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

    // Init 使用指定的选项初始化 Logger.
    func Init(opts *Options) {
    stdOnce.Do(func() {
    std = NewLogger(opts)
    })
    }
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2512 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 13:12 · PVG 21:12 · LAX 06:12 · JFK 09:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.