Go中的管道(channel)

概要

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

Go语言提倡使用通信的方法代替共享内存,这里通信的方法就是使用通道(channel),

管道(channel)特质介绍

(1)管道本质就是一个数据结构-队列
(2)数据是先进先出
(3)自身线程安全,多协程访问时,不需要加锁,channel本身就是线程安全的
(4)管道有类型的,一个string的管道只能存放string类型数据

请添加图片描述

管道的定义:

var 变量名 chan 数据类型
PS1:chan管道关键字
PS2:数据类型指的是管道的类型,里面放入数据的类型,管道是有类型的,int类型的管道只能写入整数int
PS3:管道是引用类型,必须初始化才能写入数据,即make后才能使用

例如:

package main
import(
        "fmt"
)
func main(){
        //定义管道 、 声明管道 ---> 定义一个int类型的管道
        var intChan chan int
        //通过make初始化:管道可以存放3个int类型的数据
        intChan = make(chan int,3)
        //证明管道是引用类型:
        fmt.Printf("intChan的值:%v",intChan) // 0xc000112080
        //向管道存放数据:
        intChan<- 10
        num := 20
        intChan<- num
        intChan<- 40  
        //注意:不能存放大于容量的数据:
        //intChan<- 80  
        //在管道中读取数据:
        num1 := <-intChan
        num2 := <-intChan
        num3 := <-intChan
        fmt.Println(num1)
        fmt.Println(num2)
        fmt.Println(num3)
        //注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:
        num4 := <-intChan
        fmt.Println(num4)
        //输出管道的长度:
        fmt.Printf("管道的实际长度:%v,管道的容量是:%v",len(intChan),cap(intChan))
}

管道的关闭

使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是任然可以从该管道读取数据

例如:

package main
import(
        "fmt"
)
func main(){
        //定义管道 、 声明管道
        var intChan chan int
        //通过make初始化:管道可以存放3个int类型的数据
        intChan = make(chan int,3)
        //在管道中存放数据:
        intChan<- 10
        intChan<- 20
        //关闭管道:
        close(intChan)
        //再次写入数据:--->报错
        //intChan<- 30
        //当管道关闭后,读取数据是可以的:
        num := <- intChan
        fmt.Println(num)
}

管道的遍历

管道支持for-range的方式进行遍历,有两个细节需要注意

  1. 在遍历时,如果管道没有关闭,则会出现deadlock的错误
  2. 在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历

例如:

package main
import(
        "fmt"
)
func main(){
        //定义管道 、 声明管道
        var intChan chan int
        //通过make初始化:管道可以存放3个int类型的数据
        intChan = make(chan int,100)
        for i := 0;i < 100;i++ {
                intChan<- i
        }
        //在遍历前,如果没有关闭管道,就会出现deadlock的错误
        //所以我们在遍历前要进行管道的关闭
        close(intChan)
        //遍历:for-range
        for v := range intChan {
                fmt.Println("value = ",v)
        }
        }

协程和管道协同工作案例

【1】案例需求:
请完成协程和管道协同工作的案例,具体要求:

  1. 开启一个writeData协程,向管道中写入50个整数.
  2. 开启一个readData协程,从管道中读取writeData写入的数据。
  3. 注意: writeData和readDate操作的是同一个管道
  4. 主线程需要等待writeData和readDate协程都完成工作才能退出

原理图:
请添加图片描述

package main
import(
        "fmt"
        "time"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
//写:
func writeData(intChan chan int){
        defer wg.Done()
        for i := 1;i <= 50;i++{
                intChan<- i
                fmt.Println("写入的数据为:",i)
                time.Sleep(time.Second)
        }
        //管道关闭:
        close(intChan)
}
//读:
func readData(intChan chan int){
        defer wg.Done()
        //遍历:
        for v := range intChan{
                fmt.Println("读取的数据为:",v)
                time.Sleep(time.Second)
        }
}
func main(){//主线程
        //写协程和读协程共同操作同一个管道-》定义管道:
        intChan := make(chan int,50)
        wg.Add(2)
        //开启读和写的协程:
        go writeData(intChan)
        go readData(intChan)
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()	
}

声明只读只写管道

在管道中,管道可以声明为只读或者只写性质

package main
import(
        "fmt"
)
func main(){
        //默认情况下,管道是双向的--》可读可写:
        //var intChan1 chan int
        //声明为只写:
        var intChan2 chan<- int  // 管道具备<- 只写性质
        intChan2 = make(chan int,3)
        intChan2<- 20
        //num := <-intChan2 报错
        fmt.Println("intChan2:",intChan2)
        //声明为只读:
        var intChan3 <-chan int// 管道具备<- 只读性质 
        if intChan3 != nil {
                num1 := <-intChan3
                fmt.Println("num1:",num1)
        }
        //intChan3<- 30 报错
        
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值