前情概要

最近设计了线程模型:

  1. 所有的请求在 handler 中进行简单读和计算处理,
  2. 所有关于资源竞争的操作都被提交到一个单独的线程去统一处理

因为资源竞争的处理环境是绝对串行,所以在这个单独的线程中完全无锁。这样的设计下就要求:

  1. 该线程不会被阻塞
  2. 线程中单个逻辑尽可能简单,不能成为性能的瓶颈

如果能做到这两个要求,那么一个绝对简单的线程处理逻辑就出现了,全程无锁的设计要多爽有多爽。

这个线程与上下游的交互参考生产者消费者模型,使用阻塞队列(go 中的带缓冲的 channel 天然适配)来进行共享数据,印证了 go 设计中不要使用共享内存来通信,使用通信来共享内存的理念。

但是这个设计的实现难点就是如果保证这个调度线程不会被阻塞,特别是在对 channel 进行操作的时候,如果 channel 的缓冲区被填满或者当前缓冲区没有数据怎么处理?

PS: 如果可以避免,最好不要在主流逻辑中使用可能阻塞的 channel 读,完全可以放在 select 中作为逻辑开始的信号。

是否有一种对 channel 操作无阻塞的方法?

无阻塞读

1
2
3
4
5
6
7
8
func NoBlockedRead(ch chan int) (int, error){
select{
case <- time.After(time.Nanosecond):
return 0, errors.New("read time out")
case res := <- ch:
return res, nil
}
}

无阻塞写

1
2
3
4
5
6
7
8
func NoBlockedWrite(ch chan int, input int) error {
select {
case <-time.After(time.Nanosecond):
return errors.New("write time out")
case ch <- input:
return nil
}
}

根据不同需求,可以包装成一个带 timeout 的无阻塞读/写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func TimeoutRead(timeout time.Duration, ch chan int) (int, error) {
select {
case <-time.After(timeout):
return 0, errors.New("read time out")
case res := <-ch:
return res, nil
}
}

func TimeoutWrite(timeout time.Duration, ch chan int, input int) error {
select {
case <-time.After(timeout):
return errors.New("write time out")
case ch <- input:
return nil
}
}

那么问题来了,这样的实现,会不会影响到后续的读写呢?
这就要引申出另一个问题了?
chan 内部究竟是如何实现的, 待补

reference blog