【Go】十八、管道

文章目录

1、管道

  • channel
  • 本质是一个队列,先进先出
  • 自身线程安全,多协程访问时,不用加锁,channel本身就是线程安全的
  • 一个string的管道只能存放string类型数据

2、管道的定义

go 复制代码
var  变量名  chan  数据类型
  • chan是管道的关键字
  • 数据类型,比如int类型的管道只能写入整数int
  • 管道是引用类型,必须初始化才能写入数据,即make后才能使用
  • 管道中不能存放大于容量的数据
  • 在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错

示例:

go 复制代码
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))
}

3、管道的关闭

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

go 复制代码
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)
}

4、管道的遍历

  • for
  • for-range

在遍历时,如果管道没有关闭,则报错:

go 复制代码
package main
import(
        "fmt"
)
func main(){
        //定义管道 、 声明管道
        var intChan chan int
        //通过make初始化:管道可以存放100个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)
        }
}

5、管道 + 协程

  • 开启一个writeData协程,向管道中写入50个整数.
  • 开启一个readData协程,从管道中读取writeData写入的数据
go 复制代码
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()	
}

运行:

在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错。所以上面即使写入后sleep两秒,读取也不会报错。

6、只读、只写管道

go 复制代码
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 报错
        
}

7、管道的阻塞

管道只写入数据,不读取,超过容量:

go 复制代码
package main
import(
        "fmt"
        _"time"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值

//写:
func writeData(intChan chan int){
        defer wg.Done()
        for i := 1;i <= 20;i++{   //超过容量管道的容量10
                intChan<- i
                fmt.Println("写入的数据为:",i)
                //time.Sleep(time.Second)
        }
        //管道关闭:
        close(intChan)
}

func main(){//主线程
        //定义管道:
        intChan := make(chan int,10)
        wg.Add(1)
        //开启写的协程:
        go writeData(intChan)
        wg.Wait()	
}

改一下,写的快,读的慢:没报错,但很明显,写被影响了,到最后被动等读协程,读走一个,写协程才能继续写。

8、select

  • 从多个管道中进行非阻塞的选择
  • select同时监听多个管道的数据流通,并在其中任意一个管道有数据可读或者可写的时候做相应的处理
go 复制代码
select {
case <- channel1:
    // 处理channel1的数据
case data := <- channel2:
    // 处理channel2的数据
case channel3 <- value:
    // 向channel3发送数据
default:
    // 当没有任何通道准备就绪时执行default块
}

按顺序检查有没那个case后面的管道是可读或者可写的,有则执行该case语句。如果多个case同时满足,Go会随机选择一个执行。

如果没有任何case语句满足条件,且存在default语句,则执行default块中的代码。如果没有default语句,则select语句会被阻塞,直到至少有一个case语句满足条件。

go 复制代码
package main
import(
        "fmt"
        "time"
)
func main(){
        //定义一个int管道:
        intChan := make(chan int,1)
        go func(){
                time.Sleep(time.Second * 5)
                intChan<- 10
        }()
        //定义一个string管道:
        stringChan := make(chan string,1)
        go func(){
                time.Sleep(time.Second * 2)
                stringChan<- "msbgolang"
        }()
        //fmt.Println(<-intChan)//本身取数据就是阻塞的
        select{
                case v := <-intChan :
                        fmt.Println("intChan:",v)
                case v := <-stringChan :
                        fmt.Println("stringChan:",v)
                default:
                        fmt.Println("防止select被阻塞")
        }
}

输出:

go 复制代码
防止select被阻塞

上面两个case的协程都不能立即可读或可写,走了default。没有default,则输出:

go 复制代码
msbgolang

因为其对应的管道阻塞时间短,2s后就可以读到了

相关推荐
湫ccc1 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
2401_857610032 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
凌冰_2 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举