go channel学习笔记

什么是channel

go channel 是go语言一种核心数据类型,可以理解是一种管道,并发核心单元可以通过channel发送或接收数据进行通讯,实现并发同步。

channel 类型

channel类型是可以有方向的,假设T是一种类型:

  1. chan T是双向channel类型,编译器允许对双向channel同时进行发送和接收。
  2. chan<- T是只写channel类型,编译器只允许往channel里面发送数据。
  3. <-chan T是只读channel类型,编辑器只允许从channel里面接收数据。
    在go语言中,通过chan创建通道,创建语法:
    channel1 := make(chan int)  //双向channel,可读写
    channel2 := make(chan<- int)  //单向只写channel
    channel3 := make(<-chan int)  //单向只读channel
    channel_name 表示声明的管道名,Type表示管道传输的数据类型。管道支持数据类型包括:int,string,struct等。

channel 分类

channel分无缓冲类型和带缓冲类型:

  • 无缓冲类型channel
    一个线程向这个channel发送了消息后,会阻塞当前的这个线程,直到其他线程去接收这个channel的消息。创建语法:
    intchan := make(chan int)
  • 缓冲类型channel
    带缓冲的channel,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理。创建语法:
    intchan := make(chan int,3) 

channel的一些操作

假设ch是一个通道。

  • close(关闭一个通道)
    close(ch)
    注意:如果ch是一个nil通道,或者ch已经被关闭,上述close(ch)会触发panic
  • 向通道ch发送一个值
    ch<-c
    注意:ch只能是双向通道或者是单向只写通道。
  • 从通道ch接收一个值
    c <-ch
    注意:通道ch只能是双向通道或者是单向只读通道
  • 获取通道容量
    cap(ch)
  • 获取通道长度
    len(ch)
    注意:len函数返回是当前通道元素个数,cap函数返回的是当前通道总共能存放元素个数。

一些channel的使用例子

  1. 非缓冲通道实现数据同步
package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)
    done := make(chan string)
    go func(ch chan<- int, x int) {
        time.Sleep(time.Second * 5)
        ch <- x * x * x
    }(c, 10)
    go func(ch <-chan int) {
        data := <-ch
        fmt.Println(data)
        s := "程序结束"
        done <- s
    }(c)
    fmt.Println("程序开始")
    fmt.Println(<-done)
}

结果输出:

程序开始
1000
程序结束
  1. 非缓冲通道实现超时控制
package main

import (
    "fmt"
    "time"
)

func main() {
    select {
    case <-dowork():
        fmt.Println("任务在规定时间内完成!")
    case <-time.After(time.Second * 2):
        fmt.Println("任务超时了!!!")
    }
}

func dowork() <-chan int {
    ch := make(chan int)
    go func() {
        fmt.Println("开始工作")
        time.Sleep(time.Second * 3)
        ch <- 0
    }()
    return ch
}

结果输出:

开始工作
任务超时了!!!
  1. 带缓冲通道实现生产者-消费者模型
package main

import (
    "fmt"
    "time"
)

func producer(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch chan int, done chan string) {
    for i := 0; i < 5; i++ {
        go func(id int) {
            for {
                data, ok := <-ch
                if !ok {
                    done <- "done"
                    return
                } else {
                    fmt.Printf("消费者 %v,完成任务 %v\n", id, data)
                    time.Sleep(time.Second * 2)
                }
            }
        }(i)

    }
}

func main() {
    done := make(chan string)
    ch := make(chan int, 10)
    go producer(ch)
    go consumer(ch, done)
    <-done
    fmt.Println("任务完成")
}

结果输出:

消费者 4,完成任务 0
消费者 1,完成任务 1
消费者 2,完成任务 2
消费者 3,完成任务 3
消费者 0,完成任务 4
消费者 0,完成任务 5
消费者 3,完成任务 8
消费者 2,完成任务 6
消费者 1,完成任务 9
消费者 4,完成任务 7
任务完成
  1. 带缓冲通道实现并发数量控制
package main

import (
    "fmt"
    "time"
)

func handleEvent(done chan string, task chan bool) {
    for i := 0; i < 10; i++ {
        task <- true
        go func(id int) {
            fmt.Printf("处理事件 %v\n", id)
            time.Sleep(time.Second * 1)
            <-task
            if id == 9 {
                done <- "done"
            }
        }(i)
    }

}

func main() {
    done := make(chan string)
    task := make(chan bool, 2) //并法数控制为2
    go handleEvent(done, task)
    <-done
    fmt.Println("任务完成")
}

结果输出:

处理事件 1
处理事件 0
处理事件 2
处理事件 3
处理事件 5
处理事件 4
处理事件 7
处理事件 6
处理事件 8
处理事件 9
任务完成

channel使用注意事项

  1. 关闭一个nil通道或者一个已经关闭的通道将产生一个panic。
  2. 向一个已关闭的通道发送数据也将导致panic。
  3. 向一个nil通道发送数据或者从一个nil通道接收数据将使当前协程永久阻塞。
本作品采用《CC 协议》,转载必须注明作者和本文链接
拉姆塞
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!