Golang异步协程调用中Logger阻塞问题解决方案
Golang异步协程调用中Logger阻塞问题解决方案 我的错误日志函数如下,它返回一个 *logger。
func StartLogs(logFile string) *log.Logger {
// 打开文件,如果不存在则创建
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
logger := log.New(file, "Custom Log", log.LstdFlags)
return logger
}
我像这样使用它。
myLogger:=StartLogs('logfile.log')
go DoSomething(user.Filename, myLogger)
现在,*logger 会阻止 goroutine 异步运行。 有什么方法可以执行这个操作吗?
更多关于Golang异步协程调用中Logger阻塞问题解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
你好,@Arshpreet_Singh,欢迎来到论坛。我不太确定你所说的 goroutine 无法异步运行具体指什么,但我发现你的 StartLogs 函数存在一个潜在问题:它推迟了日志文件的关闭。这意味着当 StartLogs 的调用者获得返回的 logger 时,logger 的目标文件已经关闭了,我不确定之后尝试写入日志消息时会发生什么。我建议将你的 StartLogs 函数修改为类似这样:
func StartLogs(logFile strinf) (logger *log.Logger, closer func() error) {
// existing code here, but don't defer closing the file
return logger, f.Close
}
然后,你可以在调用 StartLogs 的地方推迟调用返回的关闭函数:
logger, closer := StartLogs(logFilename)
defer closer()
// use logger
更多关于Golang异步协程调用中Logger阻塞问题解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在异步协程中使用同步Logger确实会导致阻塞问题。以下是几种解决方案:
方案1:使用带缓冲的通道实现异步日志
type AsyncLogger struct {
logger *log.Logger
ch chan string
}
func NewAsyncLogger(logFile string) *AsyncLogger {
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
logger := log.New(file, "Custom Log", log.LstdFlags)
al := &AsyncLogger{
logger: logger,
ch: make(chan string, 1000), // 缓冲大小根据需求调整
}
go al.process()
return al
}
func (al *AsyncLogger) process() {
for msg := range al.ch {
al.logger.Println(msg)
}
}
func (al *AsyncLogger) Log(msg string) {
select {
case al.ch <- msg:
// 成功写入通道
default:
// 通道满时的处理策略,例如丢弃或写入备用日志
fmt.Println("Log buffer full, dropping message:", msg)
}
}
func (al *AsyncLogger) Close() {
close(al.ch)
}
// 使用示例
func main() {
logger := NewAsyncLogger("logfile.log")
defer logger.Close()
go DoSomething(user.Filename, logger)
}
方案2:使用sync.Pool减少锁竞争
type BufferedLogger struct {
logger *log.Logger
mu sync.Mutex
pool sync.Pool
}
func NewBufferedLogger(logFile string) *BufferedLogger {
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
logger := log.New(file, "Custom Log", log.LstdFlags)
return &BufferedLogger{
logger: logger,
pool: sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
},
}
}
func (bl *BufferedLogger) Log(format string, args ...interface{}) {
go func() {
buf := bl.pool.Get().(*bytes.Buffer)
buf.Reset()
defer bl.pool.Put(buf)
fmt.Fprintf(buf, format, args...)
bl.mu.Lock()
bl.logger.Println(buf.String())
bl.mu.Unlock()
}()
}
// 使用示例
func DoSomething(filename string, logger *BufferedLogger) {
logger.Log("Processing file: %s", filename)
}
方案3:使用现有的异步日志库
// 使用zerolog示例
import "github.com/rs/zerolog"
func SetupAsyncLogger() zerolog.Logger {
file, err := os.OpenFile("logfile.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
// 创建异步writer
asyncWriter := zerolog.AsyncWriter(file)
return zerolog.New(asyncWriter).With().
Timestamp().
Str("service", "myapp").
Logger()
}
// 使用示例
func main() {
logger := SetupAsyncLogger()
go func() {
logger.Info().Str("filename", user.Filename).Msg("Processing file")
}()
}
方案4:批量写入优化
type BatchLogger struct {
logger *log.Logger
batchSize int
batch []string
mu sync.Mutex
flushTimer *time.Ticker
}
func NewBatchLogger(logFile string, batchSize int, flushInterval time.Duration) *BatchLogger {
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
logger := log.New(file, "Custom Log", log.LstdFlags)
bl := &BatchLogger{
logger: logger,
batchSize: batchSize,
batch: make([]string, 0, batchSize),
flushTimer: time.NewTicker(flushInterval),
}
go bl.autoFlush()
return bl
}
func (bl *BatchLogger) Log(msg string) {
bl.mu.Lock()
bl.batch = append(bl.batch, msg)
if len(bl.batch) >= bl.batchSize {
bl.flush()
}
bl.mu.Unlock()
}
func (bl *BatchLogger) flush() {
if len(bl.batch) == 0 {
return
}
var sb strings.Builder
for _, msg := range bl.batch {
sb.WriteString(msg)
sb.WriteString("\n")
}
bl.logger.Print(sb.String())
bl.batch = bl.batch[:0]
}
func (bl *BatchLogger) autoFlush() {
for range bl.flushTimer.C {
bl.mu.Lock()
bl.flush()
bl.mu.Unlock()
}
}
func (bl *BatchLogger) Close() {
bl.flushTimer.Stop()
bl.mu.Lock()
bl.flush()
bl.mu.Unlock()
}
选择哪种方案取决于具体需求:
- 方案1适合需要完全异步且不丢失日志的场景
- 方案2适合高并发但允许少量锁竞争的场景
- 方案3适合希望使用成熟解决方案的场景
- 方案4适合需要减少磁盘I/O次数的场景

