Go 1.21 新加的 log/slog 大家开始用了吗? 写了一个输出到阿里云日志的 writer 扩展, 顺带问个跟 slog 扩展相关的问题.

2023-09-28 15:23:40 +08:00
 Gota

先是分享下我写的扩展

扩展 writer 的地址: https://github.com/gota33/aliyun-log-writer

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

用的时候把参数中的 os.Stdout 换成这个 writer 就行了.

然后问两个问题

一个是 slog 想 Hook 的话就两个接口可以入手, slog.Handlerio.Writer.

我这里选择了 io.Writer, 主要是因为自己写 slog.Handler 的话, 得把 slog.commonHandler 里的逻辑再实现一遍, 实在有点麻烦, 有没有像 logrus.Hook 那样比较简单的实现方式?


还有一个问题跟 channel 有关.

有这样的工作队列, submit() 提交任务, stop() 阻止新任务提交, 并处理完存量任务后返回.

var (
	ErrClosed = errors.New("closed")
	chQuit    = make(chan struct{})
	chData    = make(chan int, 10)
)

// func start() { ... }

func submit(n int) error {
	select {
	case <-chQuit:
		return ErrClosed
	case chData <- n:
		return nil
	}
}

func stop() {
	close(chQuit)
	close(chData)
	
	for n := range chData {
		// process data
		_ = n
	}
}

但是 selectswitch 不一样, case 的选择不是有序的, 导致有时候会选到第二个 case 然后 panic.

所以后来把 stop() 改成这样了

func stop() {
	close(chQuit)

	ch := chData
	chData = make(chan int)

	for len(ch) > 0 {
		// process data
		select {
		case n := <-ch:
			_ = n
		default:
		}
	}
}

这种情况大家一般是怎么处理的?

3581 次点击
所在节点    Go 编程语言
40 条回复
Gota
2023-09-28 18:47:22 +08:00
@qing18 不 close(chData) 就是帖子末尾处的写法.
这里的 submit 接口需要确保: 如果不返回错误的话, 写入的 data 是一定要被处理的.
所以如果不 close 也不换成一个无缓冲 channel 的话, 会出现调用者认为数据成功提交了, 但实际上却没处理的情况.
soap520
2023-09-28 19:57:25 +08:00
```
func submit(n int) error {
select {
case <-chQuit:
return ErrClosed
default:
}

chData <- n
return nil

}

func stop() {
close(chQuit)

for n := range chData {
// process data
_ = n
}

close(chData)
}
```

看看这样行不行,
submit 里面先判断一下 chQuit 是不是已经 close 了。
stop 处理完再 close chData 。

一种可能让人看起来有点担心的执行顺序是,1. submit 里, 判断 chQuit 还没关闭。2. stop 里,执行 close(chQuit)。3. submit 里,接着 chData <- n 。不过应该在你的用例中年问题不大。
Gota
2023-09-28 20:20:11 +08:00
@soap520 你这里把 close(chData) 放到 for 循环之后执行, 那 for 循环就永远结束不了了.
realpg
2023-09-28 20:30:57 +08:00
没有
1.21 自己瞎鸡儿改 xml excel 功能都用不了,被迫退回 1.20
Gota
2023-09-28 20:40:06 +08:00
@realpg 升到 1.21.1 试试呢? 一般我都等大版本之后的一个小版本才开始正式用, 最开始那个版本确实容易出一些小问题.
soap520
2023-09-28 20:43:37 +08:00
@Gota 确实,我把 stop 改成这样是不是就可以了。
```
func stop() {
close(chQuit)

for {
select {
case n := <-chData:
_ = n
default:
close(chData)
return
}
}
}
```
realpg
2023-09-28 20:48:47 +08:00
@Gota #25
之前说了,下个小版本会合并修复
还不是 golang 自己修的,excel 那个库大佬给提的 pr
没有特别频繁检查版本的习惯,过两天再说
Gota
2023-09-28 20:55:40 +08:00
@soap520 那就剩下 #22 里你自己提到的那个 panic 问题了. 这里的用例是 slog 的 hook, 所以 submit 可能会在任意线程中被调用, 数量和时机都是没办法控制的, 也就是说 submit 里那个过了 if 之后的挂起其实很容易触发.
soap520
2023-09-28 21:14:31 +08:00
@Gota 明白了,那我把 stop 最后 close(chData)去掉是不是就行了。去掉之后看起来和你 1L 的方法就差不多了,只是没有重新给 chData 赋值(我也不清楚 slog hook 的用例里需不需要再给 chData 一个 channel )。
如果要很“完美”的话,我除了弄一个锁把 submit 里的 read chDone, enqueue data 保护起来之外想不到更好的办法了。
Gota
2023-09-28 21:25:07 +08:00
@soap520 哈哈, 异步相关的东西确实比较烧脑. 1L 重新赋值一个无缓冲 channel 是为了防止 stop 之后有数据进入 chData 却没人来处理, 随着主线程退出这份数据就丢掉了. 至于加锁, 不到万不得已最好别加, 否则每调一次 Log 整个应用都被锁一下, 就有点夸张了.
nuk
2023-09-29 03:58:37 +08:00
显然需要一个临界区,close channel 的时候要等待其他所有线程离开临界区,不管用 channel 还是 lock 来实现代价都蛮大,单个 bool 或者单个 channel close 都不可能实现,感觉 stop 就直接 drop 掉 log 会比较容易简单。
Gota
2023-09-29 10:20:05 +08:00
@nuk 直接丢数据肯定不行,正文末尾的写法也不用等其他线程呀。
nuk
2023-09-29 12:43:34 +08:00
@Gota 但是这个也不能保证 close channel 之后没有线程去写啊。。
Gota
2023-09-29 12:53:28 +08:00
@nuk 没有 close ,是换成了一个无缓冲的 channel ,从而确保能 select 到第一个 case 。
mapleray
2023-10-01 11:24:40 +08:00
@Gota #30 如果不想加锁,又不想丢日志,只能想办法把关闭操作抛给使用者了。

目前使用方式 uber-go/atomic.Bool + 缓冲 channel ,退出时等待 channel 清理完才退出。
paceewang1
2023-10-07 14:42:42 +08:00
这个场景,可以用乐观锁吧,atomic ,CAS
Jrue0011
2023-10-12 17:26:15 +08:00
读写锁的场景?
stop 写锁更新状态
submit 读锁判断状态
Gota
2023-10-12 17:47:54 +08:00
@paceewang1 #36
@Jrue0011 #37
见 #30 的回复,帖子末尾的写法是不需要锁的
paceewang1
2023-10-16 11:19:01 +08:00
@Gota CAS 也不是在 submit 里面加锁,我是指在 stop 里面加锁然后转换状态;相当于引入一个状态机而已,submit 只需要加一个状态判断就可以了,看了一下就和#13 的代码大致一样吧,但是 stop 方法先处理 chData 再关闭:
```
func stop() {
if ok := CAS(chQuit); !ok {
// return error
}

for n := range chData {
// process data
_ = n
}

close(chData)
}
```
Gota
2023-10-16 13:53:34 +08:00
@paceewang1 见#23 楼和后续的回复,还是有点问题

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

https://yangjunhui.monster/t/977965

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

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

© 2021 V2EX