go-channel实现
channel入门
channel基础
goroutines之间的通信,让它们之间可以进行数据交换。
像管道一样,一个goroutine_A向channel中放数据,另一个goroutine_B从channel取数据。
(在放和取的过程中,有互斥保证、hapen before保证,具体见unbuffer管道和buffer管道的阻塞现象)
定义及类型
ch := make(chan int) // unbuffer的chan
ch := make(chan string) //buffer的chan
chan Type表示chan中元素的类型,用<-chan Type 和 chan<- Type表示只读管道和只发管道
操作形式:
ch <- Value // 发送
<-ch //读取
val := <-ch
val, ok = <ch
属性和分类
操作
- send,发送数据
- receive,接收数据
- close,关闭
关闭后,send操作导致panic;
关闭后,recv操作时,如果有未读的数据则获取到未读数据,否则返回对应类型0值和状态码false。可利用这个性质做一些其他操作;
并非强制需要close来关闭,有时会自动关闭;
一般send端使用close,表示无数据再发送;
分类
unbuffered channel和buffered channel
unbuffered: 阻塞、同步模式
send发送数据,然后阻塞,直到receive端将此数据接收
receive一直阻塞,直到send端发送了一个数据
buffered: 非阻塞、异步
send端在容量未满时发送数据,不会阻塞
receive按FIFO从中取数据
可以认为,send给unbuffer的chan,必须等有接收者才返回;给buffer的chan,
容量未满时暂存到内部直接返回。而receive都是必须有数据才不阻塞,
而unbuffer有数据意味着有send,buffered的有数据意味中当前容量不为空。
buffered chan两个属性
容量(cap)和长度(len)
cap表示最多可以缓存多少数据
len表示已缓存数量
两种特殊chan
nil chan和chan类型chan
nil chan: 只声明未分配内存,如var ch chan int
nil channel会永远阻塞对该channel的读、写操作。
chan类型chan: 双层通道, chan的元素类型也是chan
死锁
所有的go routine都被阻塞了,就称之为死锁。
例如在主main中,声明了一个unbuffered chan,如果只做send或receive操作,则这个主main
就阻塞了,这时就会报错。
也就是说,为了保证主main中send或receive不阻塞,必须在它操作前有其他的go routine实现了
与它相反的receive或send。
使用unbufferd chan进行通信
func testChan(name string, wg *sync.WaitGroup, ch chan int) {
defer wg.Done()
for {
value, ok := <-ch
if !ok {
fmt.Printf("go routine[%v] receive close signal\n", name)
return
}
if value == 10 {
close(ch)
fmt.Printf("go routine[%v] send close signal\n", name)
return
}
value++
ch <- value
}
}
func main() {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(2)
go testChan("name1", &wg, ch)
go testChan("name2", &wg, ch)
ch <- 1
wg.Wait()
fmt.Printf("main end %v\n", "aaa")
}
这里ch <- 1必须放在go testChan后面,
使用for range迭代chan
前面都是在for无限循环中读取channel中的数据,但也可以使用range来迭代channel,它会返回每次迭代过程中所读取的数据,直到channel被关闭。
必须注意,只要channel未关闭,range迭代channel就会一直被阻塞。
func testChan(name string, wg *sync.WaitGroup, ch chan int) {
defer wg.Done()
for value := range ch {
if value == 10 {
close(ch)
fmt.Printf("go routine[%v] send close signal\n", name)
return
}
value++
ch <- value
}
}
多个管道数据传递
将数据从A读入,写入B,从B在读入,写到C…可以实现多线程按指定顺序打印。
func recvAndSendNext(name string, recv_ch chan int, send_ch chan int) {
value := <-recv_ch
value++
send_ch <- value
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
ch3 := make(chan int)
ch4 := make(chan int)
//wg.Add(1)
go recvAndSendNext("name1", ch1, ch2)
go recvAndSendNext("name2", ch2, ch3)
go recvAndSendNext("name2", ch3, ch4)
ch1 <- 0
//wg.Wait()
data := <- ch4
fmt.Printf("main end %v\n", data)
}
buffered channel异步队列请求示例
使用buffered chan, 在缓存未满时不会阻塞
type Task struct {
ID int
Status string
}
func (t *Task) run() {
time.Sleep(2 * time.Second)
t.Status = "Completed"
}
func woker(poll_chan chan Task, i int, wg *sync.WaitGroup) {
defer wg.Done()
for{
task, ok := <- poll_chan
if ok {
task.run()
}else {
return
}
}
}
func main() {
chan_pool := make(chan Task, 10) // 容量为10的chan
var wg sync.WaitGroup
// 先启动线程,不然主线程可能会阻塞
for i := 0; i < 10; i++ {
wg.Add(1)
go woker(chan_pool, i, &wg) // 从chan_pool中取数据
}
// 启动线程后,发送数据
for j := 0; j < 20; j++ {
chan_pool <- Task{ID: j, Status: "Ready"}
}
close(chan_pool)
wg.Wait()
fmt.Printf("main end")
}
select多路监听
同时监听多个事件,比如多个channel的可读、可写监听
select {
// ch1有数据时,读取到v1变量中
case v1 := <-ch1:
...
// ch2有数据时,读取到v2变量中
case v2 := <-ch2:
...
// 所有case都不满足条件时,执行default
default:
...
}
default语句,都所有语句阻塞时,会执行default,因此default必须要可执行而不能阻塞
多个case语句同时满足条件,则随机选择一个并退出select。
select的case中也可以有send操作,同样也是可以send时该case语句才认为有效。
time.After()
func After(d Duration) <-chan Time
time.After()返回一个只读chan, 注意该函数是立即返回的,不是阻塞等到Duratio时间才返回。但它是Duration后才向chan中写入完成时所处时间点。
因此将case语句中判断它是否可读,就作为了超时机制
select {
case val := <-ch1:
fmt.Println("recv value from ch1:",val)
return
// 只等待3秒,然后就结束
case <-time.After(3 * time.Second):
fmt.Println("3 second over, timeover")
}
// 源码
time.After(1 * time.Second)
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1) // 创建一个chan
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
func startTimer(*runtimeTimer)
time.Tick()
Tick(d)则是间隔地多次等待,每次等待d时长,并在每次间隔结束的时候将当前时间发送到通道。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术