golang之协程+chan通道
[管道]
分为 有缓冲和无缓冲两种
无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的。
比如:
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
例如:c1<-1
- 无缓冲: 不仅仅是向 c1 通道放 1,而是一直要等有别的携程 <-c1 接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。
- 有缓冲: c2<-1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0),只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。
无缓冲:
1)接受者与发送者必然存在于两个协程, 否则会造成互相等待 死锁的情况
2) 对于关闭的Channel,无法再进行写入, 但是可以读取,并继续向下执行
顺序执行多协程:
var ch1 = make(chan int) var stopFlag = make(chan bool) // 保证两个协程顺序执行 go func() { fmt.Println("g1") time.Sleep(3 * time.Second) ch1 <- 1 }() go func() { <-ch1 fmt.Println("g2") time.Sleep(1 * time.Second) stopFlag <- true }() <-stopFlag fmt.Println("over")
使用同步原语
func tt4() { // 2个任务顺序执行 runChan := make(chan struct{}) // 无缓冲Channel var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() println("task1 ...") time.Sleep(time.Second * 2) println("task1 !!!") runChan <- struct{}{} }() go func() { defer wg.Done() <-runChan println("task2 ...") time.Sleep(time.Second * 1) println("task2 !!!") }() wg.Wait() fmt.Println("main task finished!!!") }
有缓冲:
1.允许同一个协程中, 顺序执行, 但是保证发送的数量与接收的数量相等,否则会造成死锁
[协程]
实例:
go funct(){ ... }()
实现主进程Hold的方式
1)使用无缓冲的channel, 异常情况下实现终止操作,例如gin框架保证服务的异常中断之后的处理
2)使用select{} 即可
chan与range结合使用:
v, ok := <- ch
中的ok
仅表示是否成功读取了channel
中的数据,并不代表channel的关闭状态。
for循环的for range
形式可用于从通道接收值,直到它关闭为止。
非缓冲channel
读取和写入都会阻塞直至另一个goroutine往channel中写入和读取数据,带缓冲的channel
只有缓冲区满了,写入会阻塞,缓冲区没有数据读取会阻塞。
关闭通道
发送者可以通过关闭信道,来通知接收方不会有更多的数据被发送到channel上。
close(ch)
接收者可以在接收来自通道的数据时使用额外的变量来检查是否成功读取了数据
语法结构:
v, ok := <- ch
类似map操作,存储key,value键值对
v,ok := map[key] //根据key从map中获取value,如果key存在, v就是对应的数据,如果key不存在,v是默认值
在上面的语句中,如果ok的值是true,表示成功的从通道中读取了一个数据value。如果ok是false,这意味读取的值是通道类型的零值。
例如,如果通道是一个int通道,那么零值将为0。
示例代码:
func main() {
ch1 := make(chan int)
go sendData(ch1)
/*
子goroutine,写出数据10个
每写一个,阻塞一次,主程序读取一次,解除阻塞
主goroutine:循环读
每次读取一个,堵塞一次,子程序,写出一个,解除阻塞
发送发,关闭通道的--->接收方,接收到的数据是该类型的零值,以及false
*/
//主程序中获取通道的数据
for{
time.Sleep(1*time.Second)
v, ok := <- ch1 //其他goroutine,显示的调用close方法关闭通道。
if !ok{
fmt.Println("已经读取了所有的数据,", v, ok)
break
}
fmt.Println("取出数据:",v, ok)
}
fmt.Println("main...over....")
}
func sendData(ch1 chan int) {
// 发送方:10条数据
for i:=0;i<10 ;i++ {
ch1 <- i//将i写入通道中
}
close(ch1) //将ch1通道关闭了。
}
在上面的程序中,send Goroutine将0到9写入chl通道,然后关闭通道。主函数里有一个无限循环。它检查通道是否在发送数据后,使用变量ok关闭。如果ok是假的,则意味着通道关闭,因此循环结束。还可以打印接收到的值和ok的值。
for range 循环 channel
我们可以循环从通道上获取数据,直到通道关闭。for循环的for range形式可用于从通道接收值,直到它关闭为止。
使用range循环,示例代码:
func main() {
ch1 :=make(chan int)
go sendData(ch1)
// for循环的for range形式可用于从通道接收值,直到它关闭为止。
for v := range ch1{
fmt.Println("读取数据:",v)
}
fmt.Println("main..over.....")
}
func sendData(ch1 chan int) {
for i:=0;i<10 ; i++ {
time.Sleep(1*time.Second)
ch1 <- i
}
close(ch1)//通知对方,通道关闭
}
非缓冲通道
前面讲的所有通道基本上都没有缓冲。发送和接收到一个未缓冲的通道是阻塞的。
一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前都是阻塞的。同样的,对于接收来讲,在另一个goroutine发送之前,它也是阻塞的。
缓冲通道
缓冲通道就是指一个通道,带有一个缓冲区。发送到一个缓冲通道只有在缓冲区满时才被阻塞。类似地,从缓冲通道接收的信息只有在缓冲区为空时才会被阻塞。
可以通过将额外的容量参数传递给make函数来创建缓冲通道,该函数指定缓冲区的大小。
语法:
ch := make(chan type, capacity)
上述语法的容量应该大于0,以便通道具有缓冲区。默认情况下,无缓冲通道的容量为0,因此在之前创建通道时省略了容量参数。
示例代码
以下的代码中,channel是带有缓冲区的。
func main() {
/*
非缓存通道:make(chan T)
缓存通道:make(chan T ,size)
缓存通道,理解为是队列:
非缓存,发送还是接受,都是阻塞的
缓存通道,缓存区的数据满了,才会阻塞状态。。
*/
ch1 := make(chan int) //非缓存的通道
fmt.Println(len(ch1), cap(ch1)) //0 0
//ch1 <- 100//阻塞的,需要其他的goroutine解除阻塞,否则deadlock
ch2 := make(chan int, 5) //缓存的通道,缓存区大小是5
fmt.Println(len(ch2), cap(ch2)) //0 5
ch2 <- 100 //
fmt.Println(len(ch2), cap(ch2)) //1 5
//ch2 <- 200
//ch2 <- 300
//ch2 <- 400
//ch2 <- 500
//ch2 <- 600
fmt.Println("--------------")
ch3 := make(chan string, 4)
go sendData3(ch3)
for {
time.Sleep(1*time.Second)
v, ok := <-ch3
if !ok {
fmt.Println("读完了,,", ok)
break
}
fmt.Println("\t读取的数据是:", v)
}
fmt.Println("main...over...")
}
func sendData3(ch3 chan string) {
for i := 0; i < 10; i++ {
ch3 <- "数据" + strconv.Itoa(i)
fmt.Println("子goroutine,写出第", i, "个数据")
}
close(ch3)
}
更多使用示例:
- https://max2d.com/archives/947