在这里看到的。https://mzh.io/%E4%B8%80%E4%BA%9BGolang%E5%B0%8F%E6%8A%80%E5%B7%A7
在 nsq 中,需要读取之前磁盘上的,或者是从内存中直接读取,一般人都是先判断内存中有没有数据,然而,nsq 另辟蹊径使用了 select 语句,把 CSP 模式用到了极致。
select { case msg = <-c.memoryMsgChan: //尝试从内存中读取 case buf = <-c.backend.ReadChan(): //如果内存中没有,直接从磁盘上读取 msg, err = decodeMessage(buf) if err != nil { c.ctx.nsqd.logf("ERROR: failed to decode message - %s", err) continue }
这个想法蛮好啊。
![]() | 1 twm 2017-12-24 09:51:27 +08:00 via iPhone go 是最好的语言,go 实现的网站 www.cshome.com |
![]() | 2 Muninn 2017-12-24 10:13:58 +08:00 这样写确实挺简洁的,比我的 if else 短一些。 |
![]() | 3 pathbox 2017-12-24 10:20:33 +08:00 via iPhone 你需要多开 channel 和 goroutine,这是占一些资源的 不过可以忽略。go 语言级别支持 csp,go 的 select 是在用户态中进行对 goroutine 的调度。 |
![]() | 4 xrlin 2017-12-24 10:36:38 +08:00 但是从 channel 中取出后,channel 中的消息已经没有了,这样还需要在消费后再将消息推送进 channel,并且还要自己维护失效时间。 |
![]() | 7 gamexg 2017-12-24 11:12:41 +08:00 ![]() go 并没有承诺 select 前面的高优先级,实测也发现并不是按照顺序确定优先级,而是乱序。 ``` package main import ( "fmt" "time" ) func main() { c1 := make(chan int, 10) c2 := make(chan int, 10) go func() { for i := 0; i < 100; i++ { c1 <- 1 } }() go func() { for i := 0; i < 100; i++ { c2 <- 2 } }() time.Sleep(100 * time.Millisecond) for { select { case i := <-c1: fmt.Println(i) case i := <-c2: fmt.Println(i) } } } ``` 输出结果: 1 2 2 2 1 2 1 1 2 1 2 2 2 1 2 1 2 2 2 2 1 1 2 1 2 1 2 1 2 1 2 2 2 2 1 1 1 2 1 2 2 1 1 2 1 1 2 2 2 1 1 2 2 2 1 2 1 2 1 2 2 2 1 |
![]() | 8 pubby 2017-12-24 11:15:53 +08:00 慎用,case 后面的都会被执行,本质上是在比哪个 case 更快返回 func TestSelectCase(t *testing.T) { var fromCache = func() chan int { t.Log("call fromCache()") c := make(chan int, 1) <-time.After(time.Second * 1) c <- 1 return c } var fromDB = func() chan int { t.Log("call fromDB()") c := make(chan int, 1) <-time.After(time.Second * 2) c <- 1 return c } select { case <-fromCache(): t.Log("got from cache") case <-fromDB(): t.Log("got from db") } } select_case_test.go:11: call fromCache() select_case_test.go:21: call fromDB() select_case_test.go:34: got from db ///////////////////////// func TestSelectCase(t *testing.T) { var fromCache = func() chan int { t.Log("call fromCache()") c := make(chan int, 1) go func() { <-time.After(time.Second * 1) c <- 1 }() return c } var fromDB = func() chan int { t.Log("call fromDB()") c := make(chan int, 1) go func() { <-time.After(time.Second * 2) c <- 1 }() return c } select { case <-fromCache(): t.Log("got from cache") case <-fromDB(): t.Log("got from db") } } select_case_test.go:11: call fromCache() select_case_test.go:23: call fromDB() select_case_test.go:36: got from cache |
![]() | 9 sonyxperia 2017-12-24 11:25:45 +08:00 @twm 你给一个页面怎么看出来是 go 实现的 |
![]() | 10 pathbox 2017-12-24 11:42:04 +08:00 via iPhone @pubby 所以 select 还要考虑 switch 的条件,case 的条件同一时刻只满足一个,如果同时满足多个 调度器选择执行哪一个都是有可能的 |
![]() | 13 pathbox 2017-12-24 12:53:25 +08:00 @jpmorn 有弊端。 当缓存和硬盘中都有数据的时候, 理论上是想从缓存读取,这样速度快,才起到缓存的作用,而却从硬盘取数据了,这样即使有缓存数据也没有起到作用。 所以 这种写法只适合非常简单的一种缓存机制,即使从硬盘取也不会很慢。 真要做大规模的缓存,不适合吧,当高并发的时候,会太多缓存失效的情况会发生,而实际中这些缓存数据都是有的 |
![]() | 14 pubby 2017-12-24 13:01:52 +08:00 ![]() |
![]() | 15 wweir 2017-12-24 13:22:07 +08:00 这样做的前提是读压力小,读压力大的话,磁盘 io 直接就爆了 |
16 6ufq0VLZn0DDkL80 2017-12-24 13:30:26 +08:00 这代码逻辑有问题啊,有可能在有缓存的时候读盘,吹 go 不是这么吹的 |
![]() | 20 Reset 2017-12-24 14:46:46 +08:00 via iPhone 这个是看哪个 case 运气好被执行了 |
22 dumplinger 2017-12-25 09:39:59 +08:00 select 的一个神坑就是多个 case 之间是无序的,当都有数值的时候会随机的返回其中一个 case,巨恶心。 |
![]() | 23 zhqy 2017-12-25 11:03:07 +08:00 ![]() 代码逻辑没问题,建议看完整的代码。你贴的代码里那段中文注释错了,这里不是 if else,是 or 的关系。select 本身是无序的。 |
24 jameshuazhou 2017-12-25 11:17:55 +08:00 说 select-case 无序是坑的,官方文档早就说明得很详细了,而且这特性可以实现一些比较有意思的功能。 敢问坑在哪里? |
![]() | 25 sorra 2017-12-25 17:47:52 +08:00 两个都是 channel,应该是谁刚好有数据就选谁。channel 不可能主动去查磁盘吧?应该要别的 goroutine 从磁盘查数据放进 channel。 |