chan管道是多个goroutine进行通信的一种方式,跟linux中管道一样,linux中进程之间的通讯使用管道,管道遵循"先进后出"原则
读、写、关闭
把这3种操作和3种channel状态可以组合出9种情况:
package main
import "fmt"
func main() {
// 定义int channel,无缓冲
c := make(chan int)
// 创建一个goroutine
go func() {
defer fmt.Println("goroutine 结束")
fmt.Println("goroutine 正在运行...")
// 往c管道中写入数据
c <- 666
}()
// 从c中读取数据,并且复制给num。
num := <-c
fmt.Println("num = ", num)
fmt.Println("main goroutine 结束...")
}
num := <-c 这里会发生阻塞直到有写操作时才会往下执行。也就是管道没有数据时,读操作会阻塞。直到有一方往里写数据了,才会往下执行。总结:读数据时,如果没有数据会发生阻塞
package main
import (
"fmt"
"time"
)
func main() {
// 定义int channel,无缓冲
c := make(chan int)
// 创建一个goroutine
go func() {
defer fmt.Println("goroutine 结束")
fmt.Println("goroutine 正在运行...")
// 往c管道中写入数据
c <- 666
fmt.Println("已经往管道写数据")
}()
// 这里sleep3秒
time.Sleep(time.Second * 3)
// 从c中读取数据,并且复制给num。
num := <-c // 注意这里会阻塞直到有写操作时才会往下执行,也就是管道没有数据时,读操作会阻塞。直到有一方往里写数据了,
// 为了主程不要结束那么快
time.Sleep(time.Second * 1)
fmt.Println("num = ", num)
fmt.Println("main goroutine 结束...")
}
c <- 666 当我们再往管道写数据的时候,是阻塞的。因为这时候没有人读数据,直到3秒之后,主go程执行,读取管道里的数据,这时候,c <- 666 才会往下执行,打印出"已经往管道写数据"。结论:写数据时,如果没有人读取数据,会阻塞
package main
import (
"fmt"
"time"
)
func main() {
// 定义有缓冲的channel,容量为3
c := make(chan int, 3)
go func() {
defer fmt.Println("子go程结束...")
for i := 0; i < 3; i++ {
c <- i
fmt.Println("子go程正在运行,发送的元素=", i, "len(c)=", len(c), "cap(c)=", cap(c))
}
}()
//睡眠一下,让子go写完数据
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("num=", num)
}
fmt.Println("main 结束")
}
子go先往 c 里写3个数据,因为是有缓冲的,所以写入3个的时候,不会等待。2秒之后,主go程读取管道数据,并打印。
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
// 关闭管道
close(c)
}()
// 主go程读取数据
for {
// ok 如果为true 表示channel没有关闭,如果为false表示channel已经关闭
if data, ok := <-c; ok {
fmt.Println(data)
} else {
// 关闭了,跳出循环
break
}
}
fmt.Println("main finished..")
}
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1 * time.Second)
c <- i
}
// 关闭管道
close(c)
}()
// 主go程读取数据
//for {
// // ok 如果为true 表示channel没有关闭,如果为false表示channel已经关闭
// if data, ok := <-c; ok {
// fmt.Println(data)
// } else {
// // 关闭了,跳出循环
// break
// }
//}
// 从c中读取数据,如果没有数据就会阻塞等待数据的到来
for data := range c {
fmt.Println(data)
}
fmt.Println("main finished..")
}
使用range跟for一样的效果,range更加简洁。如果在其他协程中调用了close(ch),那么就会跳出for range循环。这也就是for range的特别之处
select功能:在多个通道上进行读或写操作,让函数可以处理多个事情,但1次只处理1个。以下特性也都必须熟记于心:
go 的select 类似linux的IO多路复用中的select事件
package main
import "fmt"
func main() {
readCh := make(chan int, 1)
writeCh := make(chan int, 1)
y := 1
select {
case x := <-readCh:
fmt.Printf("Read %d\n", x)
case writeCh <- y:
fmt.Printf("Write %d\n", y)
default:
fmt.Println("Do what you want")
}
}
我们创建了readCh和writeCh2个通道:
同时监听不同的channel,做同一件工作,可以最快的返回结果。
package main
import (
"fmt"
"net/http"
)
var q = make(chan int)
var reqNum = 0
func main() {
// 无缓冲chan
ch1 := make(chan int)
ch2 := make(chan int)
ch3 := make(chan int)
go getUrlData("https://www.baidu.com/", ch3)
go getUrlData("https://www.baidu.com/", ch2)
go getUrlData("https://www.baidu.com/", ch1)
for {
// 注意:如果3个ch都同时可读的话,select只会随机取一个。那剩下2个需要依靠for再次去读取
select {
case v := <-ch1:
fmt.Println(v)
case v := <-ch2:
fmt.Println(v)
case v := <-ch3:
fmt.Println(v)
case n := <-q:
// 3次请求之后,退出for循环
if n >= 3 {
return
}
default:
continue
}
}
fmt.Println("循环结束")
}
func getUrlData(url string, ch chan int) {
req, err := http.Get(url)
if err == nil {
ch <- req.StatusCode
reqNum += 1
q <- reqNum
} else {
fmt.Println(err)
}
}
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan interface{})
go func() {
time.Sleep(2 * time.Second)
close(c)
}()
select {
case _, ok := <-c:
fmt.Printf("chan关闭:%v", ok)
}
}
chan关闭时,第二个参数会返回false,可以根据返回值做响应的处理
在for 和select 配合使用中 使用 break 退出循环是无效的,正确的做法如下:
package main
import (
"fmt"
"time"
)
func main() {
chExit := make(chan bool)
go func() {
time.Sleep(2 * time.Second)
close(chExit)
}()
// 定义标签
loop:
for {
select {
case v, ok := <-chExit:
if !ok {
fmt.Println("close channel 1", v)
// 跳出标签
break loop
}
fmt.Println("ch1 val =", v)
}
}
fmt.Println("exit testSelectFor")
}
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
quit := make(chan bool)
//新开一个协程
go func() {
for {
select {
// case 1
case num := <-ch:
fmt.Println("num = ", num)
// case 2
case <-time.After(3 * time.Second):
// 第一次for 会执行这里进入3秒倒计时,1秒时 num有数据可读,执行case 1,select结束,执行下一次for循环
// 每次执行 time.After 把前面执行的倒计时又覆盖,又倒计时3秒
// 等到第五次循环时,num已经没有数据写入了,倒计时3秒结束后,执行 case 2 代码,打印"超时"
fmt.Println("超时")
quit <- true
}
fmt.Println(111)
}
}()
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
// 前面for循环5秒就结束了,<-quit 读取数据,由于没有数据,所以这里会阻塞,直到3秒之后,quit <- true 才会继续往下走
<-quit
fmt.Println("程序结束")
}
case <-time.After(3 * time.Second)
3秒之后会执行这个 case 。for循环每执行一次,又会重新计时3秒,所以如果num一直有数据的话,case 2是一直不会执行的。