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

目录

  • 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)

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容