Go:标准库log设计哲学与并发安全探讨,分分钟搞定

39 阅读3分钟

通过上述模型,我们可以看到Logger结构体提包含了前缀、日志标志和输出目标三个主要属性,以及它提供的一系列方法用于不同场景下的日志记录。Logger将日志输出到实现了io.Writer接口的任何对象,这提供了极高的灵活性和扩展性。

并发安全的实现方式

在Go语言的log包中,Logger的并发安全是通过在内部对写操作加锁实现的。这意味着,当多个goroutine尝试同时写入日志时,Logger能够保证每次只有一个goroutine可以写入,从而避免了数据竞争和日志信息的混乱。

  1. 互斥锁(Mutex):Go标准库中的sync.Mutex是实现并发控制的基础。Logger类型内部包含一个互斥锁,每当有写操作(如PrintlnPrintf等方法)被调用时,Logger首先锁定这个互斥锁,写操作完成后再释放锁。这个过程确保了同一时间只有一个goroutine能执行写操作。
  2. Logger的锁操作Logger的方法在进行写操作前后,会分别调用互斥锁的LockUnlock方法。这种模式在Go语言的并发编程中非常常见,是保证并发安全的有效手段。

示例代码

以下是一个简化版的Logger类型,演示了如何在output方法中使用互斥锁来确保并发安全:

type Logger struct {
	outMu sync.Mutex
	out   io.Writer // destination for output

	prefix    atomic.Pointer[string] // prefix on each line to identify the logger (but see Lmsgprefix)
	flag      atomic.Int32           // properties
	isDiscard atomic.Bool
}

// output can take either a calldepth or a pc to get source line information.
// It uses the pc if it is non-zero.
func (l \*Logger) output(pc uintptr, calldepth int, appendOutput func([]byte) []byte) error {
	if l.isDiscard.Load() {
		return nil
	}
	now := time.Now() // get this early.
	// Load prefix and flag once so that their value is consistent within
	// this call regardless of any concurrent changes to their value.
	prefix := l.Prefix()
	flag := l.Flags()
	var file string
	var line int
	if flag&(Lshortfile|Llongfile) != 0 {
		if pc == 0 {
			var ok bool
			\_, file, line, ok = runtime.Caller(calldepth)
			if !ok {
				file = "???"
				line = 0
			}
		} else {
			fs := runtime.CallersFrames([]uintptr{pc})
			f, \_ := fs.Next()
			file = f.File
			if file == "" {
				file = "???"
			}
			line = f.Line
		}
	}
	buf := getBuffer()
	defer putBuffer(buf)
	formatHeader(buf, now, prefix, flag, file, line)
	\*buf = appendOutput(\*buf)
	if len(\*buf) == 0 || (\*buf)[len(\*buf)-1] != '\n' {
		\*buf = append(\*buf, '\n')
	}
	l.outMu.Lock()
	defer l.outMu.Unlock()
	\_, err := l.out.Write(\*buf)
	return err
}

// Writer returns the output destination for the logger.
func (l \*Logger) Writer() io.Writer {
	l.outMu.Lock()
	defer l.outMu.Unlock()
	return l.out
}

在上述代码中,Logger使用sync.Mutex来确保其outputWriter方法在并发环境下是安全的。每次调用Writer方法时,都会通过互斥锁来同步访问共享资源(在这个例子中是outbuf),这保证了在任何时刻只有一个goroutine能执行写操作,从而避免了并发写入时的数据竞争问题。

通过在关键操作前后正确地加锁和解锁,Go的log包中的Logger实现了并发安全的日志记录。这种设计模式在Go语言的并发程序中广泛应用,是保证数据一致性和防止数据竞争的有效方法。

img img img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取