先介绍一下我的项目的背景,之前单位有一个做小型快递分拣机的需要,针对小型包裹智能分拣到不通的进口。大抵的物理传送带如下方图所示,原谅我不会画图。此文章的目标,只是给大家展现一下golang channel的用途。
如上图所示,传送带分了几个局部,头部区域,分拣工作区域,硬件设施(传感器和臂手)。
头部区域次要有摄像头和扫码枪,次要是辨认包裹,查问出包裹对应的区域地址。
头部区域和分拣工作区域边界,会有一个红外线传感器,来确定包裹进入了分拣工作区域。
在传送带的齿轮上会有一个速度传感器,来实时承受信号,计算传送带转动的间隔。
分拣工作区域每隔30cm会有臂手(这里咱们会有led灯做模仿,其实就是一个GPIO)
依据以上的简述,咱们用golang代码来简略实现这个逻辑
1. 功能分析
头部区域波及到扫描枪和摄像头的AI辨认,咱们就临时用一个scanPacket函数来模仿代替,
// 模仿 每隔两秒钟会有一个包裹func ScanPacket() { ticker := time.NewTicker(2 * time.Second) for { <-ticker.C fmt.Println("scan a packet") } }
当辨认到一个包裹后咱们就要确定它要在哪个led灯左近,所以咱们先要把led的配置初始化好
// Light 灯type Light struct { Id int // 灯编号 State string // on/off Distance int64 // 间隔入口红外线传感器的地位(就是距分拣工作区域起始地位的长度) 单位 mm SwitchCh chan struct{} // 用来告诉该灯亮起}var lights = map[int]*Light{ 1: { Id: 1, State: "off", Distance: 300, SwitchCh: make(chan struct{}, 10), }, 2: { Id: 2, State: "off", Distance: 600, SwitchCh: make(chan struct{}, 10), }, 3: { Id: 3, State: "off", Distance: 900, SwitchCh: make(chan struct{}, 10), }, 4: { Id: 4, State: "off", Distance: 1200, SwitchCh: make(chan struct{}, 10), },}// 此处模仿了4个led灯和对应的传送带的地位
定义包裹的构造体
// Packet 包裹type Packet struct { Id int64 // 包裹id BelongLight *Light // 所属led灯的地位 Distance int64 // 这个包裹对应的分拣工作区的地位(就是灯的地位) SensorCh chan struct{} // 传感器的channel}
因为速度传感器的io频率很高,如果把所有的packet都保护到一个数组外面,前面计算每个包裹的间隔时,锁的并发会很大,我这边就利用了goroutine的劣势,对每个包裹启动了一个goroutine,包裹的状态和间隔都是单协程计算,不存在数据抵触。
func packetWorker(packet *Packet) { //fmt.Printf("packet %d scan\n", packet.Id) defer func() { close(packet.SensorCh) }() // 1. 注册包裹的channel packetChanRegisterSets.Register(packet) // 2. 开始监控速度传感器的信号 for { <-packet.SensorCh packet.Distance = packet.Distance - 8 //fmt.Printf("packet %d distance is %d\n", packet.Id, packet.Distance) if packet.Distance >= -16 && packet.Distance <= 16 { // 3. 告诉对应的led灯亮起/(臂手拨动) packet.BelongLight.SwitchCh <- struct{}{} // 4. 勾销注册 packetChanRegisterSets.UnRegister(packet) return } }}
每个包裹实例都有一个channel, 把包裹所有的channel都注册到一个汇合外面,当承受速度传感器信号时,只须要把汇合内的所有channel发一个信号(播送),就能告诉所有的包裹从新计算所到的地位。整个零碎的并发全副集中到PacketChanRegisterSet,大大的放大了并发的范畴。大部分的并发也只是读
type PacketChanRegisterSet struct { Set sync.Map}func (s *PacketChanRegisterSet) Register(packet *Packet) { s.Set.Store(packet.Id, packet.SensorCh)}// Broadcast 播送func (s *PacketChanRegisterSet) Broadcast() { s.Set.Range(func(key, value any) bool { c := value.(chan struct{}) c <- struct{}{} return true })}func (s *PacketChanRegisterSet) UnRegister(packet *Packet) { s.Set.Delete(packet.Id)}
当led灯(或者臂手)承受到channel信号的时候就亮起
func lightWorker(light *Light) { for { <-light.SwitchCh fmt.Printf("light %d is turn on\n", light.Id) }}
2. 代码实现
package mainimport ( "fmt" "math/rand" "sync" "time")var lights = map[int]*Light{ 1: { Id: 1, State: "off", Distance: 300, SwitchCh: make(chan struct{}, 10), }, 2: { Id: 2, State: "off", Distance: 600, SwitchCh: make(chan struct{}, 10), }, 3: { Id: 3, State: "off", Distance: 900, SwitchCh: make(chan struct{}, 10), }, 4: { Id: 4, State: "off", Distance: 1200, SwitchCh: make(chan struct{}, 10), },}type PacketChanRegisterSet struct { Set sync.Map}func (s *PacketChanRegisterSet) Register(packet *Packet) { s.Set.Store(packet.Id, packet.SensorCh)}// Broadcast 播送func (s *PacketChanRegisterSet) Broadcast() { s.Set.Range(func(key, value any) bool { c := value.(chan struct{}) c <- struct{}{} return true })}func (s *PacketChanRegisterSet) UnRegister(packet *Packet) { s.Set.Delete(packet.Id)}var packetChanRegisterSets = PacketChanRegisterSet{ Set: sync.Map{},}func main() { // 启动灯 for _, light := range lights { go lightWorker(light) } // 启动扫描包裹程序 go ScanPacket() // 模仿传感器 ticker := time.NewTicker(200 * time.Millisecond) for { <-ticker.C packetChanRegisterSets.Broadcast() }}// Packet 包裹type Packet struct { Id int64 // 包裹id BelongLight *Light // 所属led灯的地位 Distance int64 // 这个包裹对应的分拣工作区的地位(就是灯的地位) SensorCh chan struct{} // 传感器的channel}// Light 灯type Light struct { Id int // 灯编号 State string // on/off Distance int64 // 间隔 单位 mm SwitchCh chan struct{}}// ScanPacket 扫描包裹func ScanPacket() { ticker := time.NewTicker(2 * time.Second) for { <-ticker.C fmt.Println("scan a packet") id := rand.Intn(4) light := lights[id+1] packet := Packet{ Id: time.Now().Unix(), BelongLight: light, Distance: light.Distance, SensorCh: make(chan struct{}, 100), } go packetWorker(&packet) }}func packetWorker(packet *Packet) { //fmt.Printf("packet %d scan\n", packet.Id) defer func() { close(packet.SensorCh) }() // 1. 注册包裹的channel packetChanRegisterSets.Register(packet) // 2. 开始监控速度传感器的信号 for { <-packet.SensorCh packet.Distance = packet.Distance - 8 //fmt.Printf("packet %d distance is %d\n", packet.Id, packet.Distance) if packet.Distance >= -16 && packet.Distance <= 16 { // 3. 告诉对应的led灯亮起/(臂手拨动) packet.BelongLight.SwitchCh <- struct{}{} // 4. 勾销注册 packetChanRegisterSets.UnRegister(packet) return } }}func lightWorker(light *Light) { for { <-light.SwitchCh fmt.Printf("light %d is turn on\n", light.Id) }}