美文网首页
GO学习笔记(18) - 并发编程(2)- Channel操作

GO学习笔记(18) - 并发编程(2)- Channel操作

作者: 卡门001 | 来源:发表于2021-07-19 16:21 被阅读0次

目录

  • channel 介绍
  • channel创建
  • channel 操作符与Range
  • channel读写与关闭
  • channel类型-有缓存与无缓存
  • channel阻塞
  • 例1:channel定义
  • 例2:缓冲区channel定义
  • 例3:channel通知结束-通过通信来共享内存
  • 例4:channel用在goroutine之间的同步
  • 例5:channel等待结束结束-sync.WaitGroup

channel 介绍

channel 提供了一种通信机制,通过它,一个 goroutine 可以想另一 goroutine 发送消息。channel 本身还需关联了一个类型,也就是 channel 可以发送数据的类型。例如: 发送 int 类型消息的 channel 写作 chan int 。

channel 是 goroutine 之间通信的一种方式,可以类比成 Unix 中的进程的通信方式管道。

channel创建

和 map 和 slice 数据类型一样channel必须先创建再使用。

ch := make(chan int)

c和 map 类似,make 创建了一个底层数据结构的引用,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true。

channel操作符与range(遍历)

操作符

它的操作符是箭头 <-,其中(箭头的指向就是数据的流向。

ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接收数据,并将数据赋值给v
Range(遍历):for …… range语句可以处理Channel。
func main() {
    go func() {
        time.Sleep(1 * time.Hour)
    }()
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i = i + 1 {
            c <- i
        }
        close(c)
    }()
    for i := range c {
        fmt.Println(i)
    }
    fmt.Println("Finished")
}

channel读、写与关闭

向channel写(发送)数据
// 向channel中发送(写)数据。
ch <- x //send
从channel读(接收)数据
//receive:从 channel 中取(读) 出数据, ok用于判断channel是否关闭
x, ok := <- ch 
//或
x = <- ch
channel关闭
close(chan变量)

与关闭相关的注意以下事项:

  • 关闭一个未初始化(nil) 的 channel 会产生 panic
  • 重复关闭同一个 channel 会产生 panic
  • 向一个已关闭的 channel 中发送消息会产生 panic
  • 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
  • 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息

channel类型

channel 分为不带缓存的 channel 和带缓存的 channel。

无缓存的 channel

从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。

通过无缓存的 channel 进行通信时,接收者收到数据 happens before 发送者 goroutine 唤醒。

有缓存的 channel

有缓存的 channel 的声明方式为指定 make 函数的第二个参数,该参数为 channel 缓存的容量

ch := make(chan int, 10)

有缓存的 channel 类似一个阻塞队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3

// blocked, send to full buffered channel
ch <- 4

通过 len 函数可以获得 chan 中的元素个数,通过 cap 函数可以得到 channel 的缓存长度。

blocking

默认情况下,发送和接收会一直阻塞着,直到另一方准备好。
这种方式可以用来在gororutine中进行数据同步,而不必使用显示的锁或者条件变量

import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    // receive from c
    //x, y := <-c, <-c这句会一直等待计算结果发送到channel中
    x, y := <-c, <-c 
    fmt.Println(x, y, x+y)
}

综合实例

例1:channel定义

/**
 * chan <- string:<- 代表发数据
 * <- chan int: 代表收数据
 */
func createWorker(id int)  chan int {
    c := make(chan int)
    for {
        fmt.Printf("worker %d receiver %d \n", id, n)
    }
    return c
}

func main()  {
    var channels [10]chan int
    for i :=0; i<10 ; i++{
        channels[i] = createWorker(i)
    }
    //worker只有有输入channel时,才打有数据打印输出
    //1.分发数据
    for i :=0; i<10 ; i++{
        //string('a' + i) : a会因为i而变为b,c,d,f..
        channels[i] <- 'a' + i
    }

    for i :=0; i<10 ; i++{
        //<- 发送数据的意思
        channels[i] <- 'A' + i
    }
    time.Sleep(time.Microsecond)
}

例2:缓冲区channel定义

package main

import (
    "fmt"
    "time"
)

func write(ch chan int){
    for i := 0; i < 100; i++ {
        ch <- i
    }
}

func read(ch chan int){
    for {
        b,ok := <- ch
        if ok == false {
            fmt.Println("chan is close")
            break
        }
        fmt.Println(b)
    }
}

func main(){
    intChan := make(chan int, 10)
    go write(intChan)
    go read(intChan)
    time.Sleep(10*time.Second)
}

例3:channel通知结束-通过通信来共享内存

package main

import (
    "fmt"
)

type worker struct {
    in chan  int


    done chan bool
}
/**

 */
func  doWorker(id int,c chan int,done chan  bool)  {
    for n := range c {  //待到c 被close时,退出
        fmt.Printf("worker %d receiver %d \n", id, n)
        //done <- true //必须有人收,否则会 fatal error: all goroutines are asleep - deadlock!
        //修改如下
        go func() {done <- true}()
    }
}
/**
 * chan <- string:<- 代表发数据
 * <- chan int: 代表收数据
 */
func createWorker(id int) worker {
    w := worker{
        in: make(chan int),
        done: make(chan bool),
    }
    //处理
    go doWorker(id,w.in,w.done)
    return w
}


//func closeChannel(w worker)  {
//  close(w.in)
//  w.done <- false
//}

func channelDemo()  {
    var workers [10]worker
    for i :=0; i<10 ; i++{
        workers[i] = createWorker(i)
    }


    //worker只有有输入channel时,才打有数据打印输出
    //1.发数据
    for i ,_:= range workers {
        workers[i].in <- 'a' + i
    }

    //发数据
    for i ,_:= range workers {
        //<- 发送数据的意思
        workers[i].in <- 'A' + i
    }

    for _,worker := range workers{
        <- worker.done
        <- worker.done
    }

}

func main() {
    channelDemo()
    //time.Sleep(time.Microsecond)
    //bufferChannel()
    //channelClose()
}

例4:channel用在goroutine之间的同步

channel可以用在goroutine之间的同步。worker做完任务后只需往channel发送一个数据就可以通知main goroutine任务完成。

import (
    "fmt"
    "time"
)
func worker(done chan bool) {
    time.Sleep(time.Second)
    // 通知任务已完成
    done <- true
}
func main() {
    done := make(chan bool, 1)
    go worker(done)
    // 等待任务完成
    <-done
}

例4:channel等待结束结束-sync.WaitGroup

package main

import (
    "fmt"
    "sync"
)

type worker struct {
    in chan  int
    wg *sync.WaitGroup
}
/**
sync.WaitGroup方法:

var wg sync.WaitGroup
wg.add
wg.done
wg.wait
 */
func  doWorker(id int,c chan int,wg *sync.WaitGroup)  {
    for n := range c {  //待到c 被close时,退出
        fmt.Printf("worker %d receiver %d \n", id, n)
        wg.Done()
    }
}

func createWorker(id int,wg *sync.WaitGroup) worker {
    w := worker{
        in: make(chan int),
        wg: wg,
    }
    //处理
    go doWorker(id,w.in,wg)
    return w
}


func channelDemo()  {
    var wg sync.WaitGroup
    var workers [10]worker
    for i :=0; i<10 ; i++{
        //只是传递地址给到createWoker
        workers[i] = createWorker(i,&wg)
    }

    //增加20个任务等待结束
    wg.Add(20)
    //1.发数据
    for i ,_:= range workers {
        workers[i].in <- 'a' + i
        //或者 循环内: wg.Add(1)
    }

    //发数据: <-
    for i ,_:= range workers {
        workers[i].in <- 'A' + i
    }

    wg.Wait()
}

func main() {
    channelDemo()
}

例5: 收发数据
package main

import (
    "fmt"
    "time"
)

/**
 * chan <- string:<- 代表发数据
 * <- chan int: 代表收数据
 */
func createWorker(id int,c chan string)  chan string {
    for {
        fmt.Printf("worker %d receiver %s \n",id, <- c)
    }
    return c
}

func channelDemo()  {
    var channels [10]chan string
    for i :=0; i<10 ; i++{
        channels[i] = make(chan string)
        go createWorker(i,channels[i])
    }

    //worker只有有输入channel时,才打有数据打印输出

    //1.分发数据
    for i :=0; i<10 ; i++{
        //string('a' + i) : a会因为i而变为b,c,d,f..
        channels[i] <- string('a' + i)
    }

    for i :=0; i<10 ; i++{
        //<- 发送数据的意思
        channels[i] <- string('A' + i)
    }
    time.Sleep(time.Microsecond)

}

func main() {
    channelDemo()
    time.Sleep(time.Microsecond)

}

相关文章

  • GO学习笔记(18) - 并发编程(2)- Channel操作

    目录 channel 介绍 channel创建 channel 操作符与Range channel读写与关闭 ch...

  • Go 专栏|并发编程:goroutine,channel 和 s

    原文链接: Go 专栏|并发编程:goroutine,channel 和 sync[https://mp.weix...

  • go语言并发操作

    go语言实现并发 通过go 实现并发操作 执行上述代码,会间隔执行say方法 通过channel实现并发数据间的通...

  • Go并发编程Channel

    Channel 单纯地将函数并发执行是没有意义地,函数与函数需要交换数据才能体现并发执行函数地意义。Go语言的并发...

  • Go语言——原子操作

    Go语言——原子操作 参考: 《Go并发编程实战(第2版)》 Background 原子操作即执行过程不能被中断的...

  • go并发通信

    go并发编程时,请记住:“不要通过共享内存来通信,而应该通过通信来共享内存” channel是Go语言在语言级别提...

  • 小试牛刀

    go 使并发编程变得简单 当一个 channel 被 close 后再 send 会 panic, 再取值不会阻塞...

  • go channel详解之源码分析

    作为golang并发编程思想的重要组成,channel(通道)非常重要,和goroutine(go协程)一起使用,...

  • Go的并发机制:线程模型

    目录 一、 Go的并发机制:线程模型二、 Go的并发机制:goroutine和channel[https://ww...

  • Go并发编程-channel多路复用

    在前面两篇文章中,已经详细的介绍了 goroutine 和 channel,它们是 Go 并发编程的基础。今天这篇...

网友评论

      本文标题:GO学习笔记(18) - 并发编程(2)- Channel操作

      本文链接:https://www.haomeiwen.com/subject/coqhmltx.html