通过上述模型,我们可以看到Logger结构体提包含了前缀、日志标志和输出目标三个主要属性,以及它提供的一系列方法用于不同场景下的日志记录。Logger将日志输出到实现了io.Writer接口的任何对象,这提供了极高的灵活性和扩展性。
并发安全的实现方式
在Go语言的log包中,Logger的并发安全是通过在内部对写操作加锁实现的。这意味着,当多个goroutine尝试同时写入日志时,Logger能够保证每次只有一个goroutine可以写入,从而避免了数据竞争和日志信息的混乱。
- 互斥锁(Mutex):Go标准库中的
sync.Mutex是实现并发控制的基础。Logger类型内部包含一个互斥锁,每当有写操作(如Println、Printf等方法)被调用时,Logger首先锁定这个互斥锁,写操作完成后再释放锁。这个过程确保了同一时间只有一个goroutine能执行写操作。 - Logger的锁操作:
Logger的方法在进行写操作前后,会分别调用互斥锁的Lock和Unlock方法。这种模式在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来确保其output和Writer方法在并发环境下是安全的。每次调用Writer方法时,都会通过互斥锁来同步访问共享资源(在这个例子中是out和buf),这保证了在任何时刻只有一个goroutine能执行写操作,从而避免了并发写入时的数据竞争问题。
通过在关键操作前后正确地加锁和解锁,Go的log包中的Logger实现了并发安全的日志记录。这种设计模式在Go语言的并发程序中广泛应用,是保证数据一致性和防止数据竞争的有效方法。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新