先介绍一下我的项目的背景,之前单位有一个做小型快递分拣机的需要,针对小型包裹智能分拣到不通的进口。大抵的物理传送带如下方图所示,原谅我不会画图。此文章的目标,只是给大家展现一下 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 main
import (
"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)
}
}