go channel 管道

简介:

@[toc]
在这里插入图片描述

概述

协程是并发编程的基础,而管道(channel)则是并发中协程之间沟通的桥梁,很多时候我们启动一个协程去执行完一个操作,执行操作之后我们需要返回结果,或者多个协程之间需要相互协作。


channel 基本使用

创建 channel

var ch0 chan int
var ch1 chan string
var ch2 chan map[string]string
type stu struct{}
var ch3 chan stu
var ch4 chan *stu

channel 方向 <-

ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接收数据,并将数据赋值给v

它包括三种类型的定义。可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。

chan T          // 可以接收和发送类型为 T 的数据
chan<- float64  // 只可以用来发送 float64 类型的数据
<-chan int      // 只可以用来接收 int 类型的数据

两个方向容易记混,这样,就默认我们是没有被显式写出的,那么 chan<- meme <-chan 就好记了吧。

在通讯(communication)开始前channel和expression必选先求值出来(evaluated),比如下面的(3+4)先计算出7然后再发送给channel。

c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)

send被执行前(proceed)通讯(communication)一直被阻塞着。如前所言,无缓存的channel只有在receiver准备好后send才被执行。如果有缓存,并且缓存未满,则send会被执行往一个已经被close的channel中继续发送数据会导致run-time panic。往nil channel中发送数据会一致被阻塞着。

<-ch用来从channel ch中接收数据,这个表达式会一直被block,直到有数据可以接收。
从一个nil channel中接收数据会一直被block。

从一个被close的channel中接收数据不会被阻塞,而是立即返回,接收完已发送的数据后会返回元素类型的零值(zero value)。

如前所述,你可以使用一个额外的返回参数来检查channel是否关闭。

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

如果OK 是false,表明接收的x是产生的零值,这个channel被关闭了或者为空。


make进行初始化

var ch0 chan int =make(chan int)
var ch1 chan int =make(chan int,10)

通过缓存的使用,可以尽量避免阻塞,提供应用的性能。
容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。
如果没有设置容量,或者容量设置为0, 说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking)。如果设置了缓存,就有可能不发生阻塞, 只有buffer满了后 send才会阻塞, 而只有缓存空了后receive才会阻塞。一个nil channel不会通信。
无缓存的与有缓存channel有着重大差别,那就是一个是同步的 一个是非同步的。

你可以在多个goroutine从/往 一个channel 中 receive/send 数据, 不必考虑额外的同步措施。


关闭 chan

这个要提一下,因为昨天写那个 “生成消费者” 就因为关闭出问题了。

使用内置函数close进行关闭,chan关闭之后,for range遍历chan中已经存在的元素后结束。
使用内置函数close进行关闭,chan关闭之后,没有使用for range的写法,需要使用,v, ok := <- ch进行判断chan是否关闭。

package main

import "fmt"

func main() {

    var ch chan int
    ch = make(chan int, 5)
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
    for {
        var b int
        b, ok := <-ch
        if ok == false {
            fmt.Println("chan is close")
            break
        }
        fmt.Println(b)
    }
}

这个是很正常的。

如果将close(ch)注释掉,意思是不关闭管道,那么会出现dead lock死锁。因为存入管道5个数字,然后无限取数据,会出现死锁。

向 closed channel 发送数据引发 panic 错误,接收立即返回零值。而 nil channel, 无论收发都会被阻塞。

package main

func main() {
    ch := make(chan int, 1)
    close(ch)
    ch <- 2
}

range 遍历 chan

package main

import "fmt"

func main() {
    var ch chan int
    ch = make(chan int, 10)
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
    for v := range ch {
        fmt.Println(v)
    }
}

同样如果将close(ch)注释掉,意思是不关闭管道,那么会出现dead lock死锁。


内置函数len()、cap()

len 返回未被读取的缓冲元素数量,cap 返回缓冲区大小。

package main

import "fmt"

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int, 3)
    ch2 <- 1
    fmt.Println(len(ch1), cap(ch1))
    fmt.Println(len(ch2), cap(ch2))
}

select

select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
select语句选择一组可能的send操作和receive操作去处理。

package main

import (
    "fmt"
)

func main() {
    ch1 := make(chan int, 1)
    ch1 <- 1
    ch2 := make(chan int, 1)
    ch2 <- 2
    select { //随机读数
    case k1 := <-ch1:
        fmt.Println(k1)
    case k2 := <-ch2:
        fmt.Println(k2)
    default:
        fmt.Println("chan")
    }
}

timeout

select有很重要的一个应用就是超时处理。 因为上面我们提到,如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

channel单向

可以将 channel 隐式转换为单向队列,只收或只发。
不能将单向 channel 转换为普通 channel。

package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 3)
    var send chan<- int = c // send-only
    var recv <-chan int = c // receive-only
    send <- 5               // <-send               // Error: receive from send-only type chan<- int
    val, ok := <-recv
    if ok {
        fmt.Println(val)
    } // recv <- 2           // Error: send to receive-only type <-chan int
}

可以试着将 send 和 recv 的顺序反过来看看。


chan 作为 chan数

channel 是第一类对象,可传参 (内部实现为指针) 或者作为结构成员。

package main

import "fmt"

type Request struct {
    data []int
    ret  chan int
}

func NewRequest(data ...int) *Request {
    return &Request{data, make(chan int, 1)}
}

func Process(req *Request) {
    x := 0
    for _, i := range req.data {
        x += i
    }
    req.ret <- x
}
func main() {
    req := NewRequest(10, 20, 30)
    Process(req)
    fmt.Println(<-req.ret)
}
相关文章
|
21天前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
|
21天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
28天前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
1月前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
2月前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel
在这个快节奏的技术时代,Go语言以其简洁的语法和强大的并发能力脱颖而出。本文将带你深入Go语言的并发机制,探索goroutine的轻量级特性和channel的同步通信能力,让你在高并发场景下也能游刃有余。
|
2月前
|
存储 安全 Go
探索Go语言的并发模型:Goroutine与Channel
在Go语言的多核处理器时代,传统并发模型已无法满足高效、低延迟的需求。本文深入探讨Go语言的并发处理机制,包括Goroutine的轻量级线程模型和Channel的通信机制,揭示它们如何共同构建出高效、简洁的并发程序。
|
6月前
|
Go
go之channel关闭与广播
go之channel关闭与广播
|
6月前
|
存储 缓存 Go
Go语言并发编程(三)——初窥管道
Go语言并发编程(三)——初窥管道
|
2月前
|
存储 Go 调度
深入理解Go语言的并发模型:goroutine与channel
在这个快速变化的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你穿越Go语言的并发世界,探索goroutine的轻量级特性和channel的同步机制。摘要部分,我们将用一段对话来揭示Go并发模型的魔力,而不是传统的介绍性文字。
|
2月前
|
安全 Go 调度
探索Go语言的并发模型:Goroutine与Channel的魔力
本文深入探讨了Go语言的并发模型,不仅解释了Goroutine的概念和特性,还详细讲解了Channel的用法和它们在并发编程中的重要性。通过实际代码示例,揭示了Go语言如何通过轻量级线程和通信机制来实现高效的并发处理。