如果 thread 执行时条件不符,就停下等待,等到条件符合时再开始开始执行
当设计线上即时显示留言板时,会接受大量的留言,并将其显示出来,我们需确保留言不出现 race condition,也需确保没留言时不执行显示。
相关的 code 在Github - go-design-patterns。
实作有问题的系统如下,使用者不断用SendMessage()
发送留言cool
至相当於 queue 的 m.messages slice,Show()
会不断读取 m.messages 显示,并移除讯息:
package main
import (
"fmt"
"math/rand"
"time"
)
type MessageBoard struct {
messages []string
}
func (m *MessageBoard) SendMessage(message string) {
m.messages = append(m.messages, message)
}
func (m *MessageBoard) Show() {
for {
fmt.Println(m.messages[0])
m.messages = m.messages[1:]
}
}
func main() {
messageBoard := new(MessageBoard)
go func() {
for {
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟各个client发送的随机处理时间
messageBoard.SendMessage("cool")
}
}()
go func() {
for {
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟各个client发送的随机处理时间
messageBoard.SendMessage("cool")
}
}()
go func() {
messageBoard.Show()
}()
time.Sleep(10 * time.Second) //等待goroutine执行完毕
}
由於 m.messages 可能不存在讯息,此时读取会发生 error 如下:
需要在 m.messages 读取时,新增执行条件,即「m.messages 为空的时候等待而不读取,等到有讯息实再读取显示」,此时可以透过 channel 来实现,将 slice 改为 channel 如下
package main
import (
"fmt"
"math/rand"
"time"
)
type MessageBoard struct {
messages chan string
}
func (m *MessageBoard) SendMessage(message string) {
m.messages <- message
}
func (m *MessageBoard) Show() {
for {
fmt.Println(<-m.messages)
}
}
func CreateMessageBoard() *MessageBoard {
messageBoard := new(MessageBoard)
messageBoard.messages = make(chan string, 100) //可容纳100条讯息
return messageBoard
}
func main() {
messageBoard := CreateMessageBoard()
go func() {
for {
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟各个client发送的随机处理时间
messageBoard.SendMessage("cool")
}
}()
go func() {
messageBoard.Show()
}()
time.Sleep(10 * time.Second) //等待goroutine执行完毕
}
channel 有几个特性:
e.g. make(chan string)
,写入後如果没有读取,会在写入处等待e.g. make(chan string, 100)
,写入後如果没有读取,可以继续写入,直到到达 buffer 数才会等待读取所以在程序码中,当 m.messages 不存在讯息时,Show()
会等待到有讯息在进行显示。channel 可容纳 100 条讯息,不会让SendMessage()
每条讯息都要等到Show()
显示完才能继续发送。
事实上,Guarded Suspension Pattern 经典的做法是采用值来当作「执行条件」,以此范例来说就是:
for len(m.messages) != 0 {
fmt.Println(m.messages[0])
m.messages = m.messages[1:]
}
并且为了避免 for 回圈一直重复检查len(m.messages) != 0
让 cpu 飙升,所以需使用sync.Cond{}
的Wait()
来让 goroutine 暂停,并在SendMessage()
时用Signal() or Broadcast()
启动已暂停的 goroutine,其实相当於 java synchronized 的Wait()
与Notify() or notifyAll()
。
但 golang 提倡「share memory by communicating」,即 CSP(Communication Sequential Process)的目标之一,透过 channel 来在不同 goroutine 间分享资料,如果以len(m.messages) != 0
来实作执行条件即是「communicate by sharing memory」,我们需透过Lock()
、Unlock()
来保护 m.messages,也需透过Wait()
、Signal()
来避免 goroutine 的执行与否,程序码会复杂较多,而 channel 可以简单乾净的实现以上需求,因此多数 gopher 面对 Guarded Suspension Pattern 会采用 channel 而非sync.Cond{}
,甚至有着废除sync.Cond{}
的issue,不过在更复杂的场景与效能考量上,例如唤起不同停住的 goroutine,所以sync.Cond{}
还是有存在的必要,所以此方案目前没有被采用。
同事 Vic 协助校稿
>>: 【心得】你今天青蛙了吗?flex之路-flex设定了宽却没有用???
鬼故事 - 这不是後门这是工程模式! Credit: sandserif 故事开始 故事回到小弯的公...
前情提要 将英雄们显示在 Mat-Card 上後,我们进一步地要对英雄资料做点加工,并且制作英雄详细...
今天要来研究一些常见的事件,来看看有那些东西会被系统纪录下来,他们的意义又是什麽。 笔者查了查发现不...
今天要来做一下粒子系统 首先要来了解 为什麽要了解 class生成物件呢 大量快速建立同类型的物件 ...
this 在 JavaScript 里,this 指向 window,在 function 中, t...