go学习笔记——通道

通道

1.通道定义

通道(channels) 是连接多个协程的管道。 你可以从一个协程将值发送到通道,然后在另一个协程中接收。

定义:make(chan string)
发送消息:channel <-
接收消息:<-channel

package main

import "fmt"

func main() {
    msgChan := make(chan string)

    go func(){ msgChan <- "ping" }()

    msg := <-msgChan
    fmt.Println("msg",msg)
}

2.通道缓冲

默认情况下,通道是 无缓冲 的,这意味着只有对应的接收(<- chan) 通道准备好接收时,才允许进行发送(chan <-)。 有缓冲通道 允许在没有对应接收者的情况下,缓存一定数量的值。

package main

import "fmt"

func main() {

    messages := make(chan string, 2)

    messages <- "buffered"
    messages <- "channel"

    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

3.通道同步

我们可以使用通道来同步协程之间的执行状态。 这儿有一个例子,使用阻塞接收的方式,实现了等待另一个协程完成。

package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    done <- true
}

func main() {

    done := make(chan bool, 1)
    go worker(done)

    <-done
}

如果你把 <- done 这行代码从程序中移除, 程序甚至可能在 worker 开始运行前就结束了。

4.通道方向

当使用通道作为函数的参数时,你可以指定这个通道是否为只读或只写。 该特性可以提升程序的类型安全。

定义只写通道:pings chan<- string
定义只读通道:pings <-chan string

package main

import "fmt"

func ping(pings chan<- string, msg string) {
    pings <- msg
}

func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

  • ping 函数定义了一个只能发送数据的(只写)通道。 尝试从这个通道接收数据会是一个编译时错误。

  • pong 函数接收两个通道,pings 仅用于接收数据(只读),pongs 仅用于发送数据(只写)。

5.通道选择器

Go 的 选择器(select) 让你可以同时等待多个通道操作。 将协程、通道和选择器结合,是 Go 的一个强大特性。

当有多个通道的值需要接收时,如果用常规写法,则会根据代码书写前后阻塞的进行数据接收。也就是数据接收会有先后顺序。
如果使用select选择器,则可以同时接收多个通道的值。
代码示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(3 * time.Second)
        c1 <- "one"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()

    // 常规写法
    // msg1 := <-c1
    // msg2 := <-c2
    // fmt.Println("msg1:", msg1)
    // fmt.Println("msg2:", msg2)

    // select选择器
    for i := 0; i < 2; i++ {
        select {
        case msg := <-c1:
            fmt.Println("received", msg)
        case msg := <-c2:
            fmt.Println("received", msg)
        }
    }
}
// output
// 常规写法
//msg1: one
//msg2: two

// select选择器
//received two
//received one

常规写法特点

  1. 顺序执行:

    • 代码会先从 c1 通道接收消息并赋值给 msg1,然后再从 c2 通道接收消息并赋值给 msg2。
    • 如果 c1 或 c2 中没有消息,程序会阻塞在对应的接收操作上,直到通道有数据可用。
  2. 缺乏灵活性:

    • 如果 c1 长时间没有消息,而c2已经有消息,程序仍然会卡在 c1 的接收操作上,无法处理 c2 的消息。
  3. 适合简单场景:

    • 当你确定 c1 和c2都会按顺序提供消息时,这种写法是可以接受的。

select选择器特点

  1. 并发处理:

    • select 语句会监听多个通道(c1和c2),并在任意一个通道有消息时执行对应的 case分支。如果c1和c2都没有消息,select 会阻塞,直到至少一个通道有数据。
  2. 灵活性更高:

    • 无论 c1 还是 c2先收到消息,程序都会立即处理,而不会因为某个通道阻塞而卡住整个流程。
  3. 随机性:

    • 如果多个通道同时有消息,select会随机选择一个通道执行对应的分支。
  4. 适合动态场景:

    • 当你需要处理多个通道的消息,并且无法预知消息的到达顺序时,select 是更好的选择。

6.通道超时处理

超时 对于一个需要连接外部资源, 或者有耗时较长的操作的程序而言是很重要的。 得益于通道和 select,在 Go 中实现超时操作是简洁而优雅的。

package main

import (
    "fmt"
    "time"
)

func main() {

    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()

    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }

    c2 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "result 2"
    }()
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}
  • 这里是使用 select 实现一个超时操作。 res := <- c1 等待结果,<-time.After 等待超时(1秒钟)以后发送的值。 由于 select 默认处理第一个已准备好的接收操作, 因此如果操作耗时超过了允许的 1 秒的话,将会执行超时 case。

  • time.After 返回一个通道,在指定时间(这里是 1 秒)后会向该通道发送一个值。代码从这个通道中接收值并打印出来。需要注意的是,这里的 res 实际上是一个 time.Time 类型的值,表示超时发生的时间。

7.非阻塞通道操作

常规的通过通道发送和接收数据是阻塞的。 然而,我们可以使用带一个 default 子句的 select 来实现 非阻塞 的发送、接收,甚至是非阻塞的多路 select。

package main

import "fmt"

func main() {
    messages := make(chan string)
    signals := make(chan bool)

    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    default:
        fmt.Println("no message received")
    }

    msg := "hi"
    select {
    case messages <- msg:
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }

    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    case sig := <-signals:
        fmt.Println("received signal", sig)
    default:
        fmt.Println("no activity")
    }
}

以上代码之所以会走default分支,是因为messages和signal通道都是无缓冲的通道,通道接受和发送都是阻塞的。

  • 发送操作(messages <- msg)会阻塞,直到有 Goroutine 从通道中接收数据。
  • 接收操作(<-messages)会阻塞,直到有 Goroutine 向通道发送数据。

8.通道关闭

关闭 一个通道意味着不能再向这个通道发送值了。 该特性可以向通道的接收方传达工作已经完成的信息。

关闭通道:close(chan)

package main

import "fmt"

func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    <-done
}

9.通道遍历

常见的通道遍历方法有以下两种:

  1. 使用forrange循环

    for job := range jobs {
        fmt.Println("received job", job)
    }
    fmt.Println("received all jobs")
  2. 使用for和显式接收

    for {
        job, more := <-jobs
        if more {
            fmt.Println("received job", job)
        } else {
            fmt.Println("received all jobs")
            break
        }
    }
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇