通道
1635字约5分钟
2025-02-03
通道Channels
通道(channels) 是连接多个协程的管道。 你可以从一个协程将值发送到通道,然后在另一个协程中接收。
这个演示demo涵盖了通道的基本概念,包括创建通道、发送值到通道、从通道接收值以及有缓冲通道的使用。
package main
import "fmt"
func main() {
    // 创建一个无缓冲的通道
    ch := make(chan int)
    // 启动一个 goroutine 将值发送到通道
    go func() {
        ch <- 10
    }()
    // 从通道接收值并打印
    value := <-ch
    fmt.Println("Received value from channel:", value)
    // 创建一个有缓冲的通道
    bufferedCh := make(chan string, 3)
    // 将值发送到有缓冲的通道
    bufferedCh <- "Hello"
    bufferedCh <- "from"
    bufferedCh <- "channel"
    // 从有缓冲的通道接收值并打印
    fmt.Println(<-bufferedCh)
    fmt.Println(<-bufferedCh)
    fmt.Println(<-bufferedCh)
}通道同步
在这个演示demo中,我们首先创建了一个用于通知任务完成的通道done,并在worker函数内部模拟了一个耗时的任务。在worker函数中,任务执行完毕后,我们向done通道发送了一个完成信号。在main函数中,我们启动了一个goroutine执行任务,并在主线程中等待从通道中接收到完成信号。一旦接收到完成信号,我们打印出“work completed”
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)
    // 启动一个 goroutine 执行任务
    go worker(done)
    // 等待任务完成,阻塞直到从通道中接收到完成信号
    <-done
    fmt.Println("work completed")
}通道方向
当使用通道作为函数的参数时,你可以指定这个通道是否为只读或只写。 该特性可以提升程序的类型安全。
package main
import "fmt"
// 通道只能用于发送数据的函数(只写)
func sendData(ch chan<- int, data int) {
    fmt.Println("Sending", data)
    ch <- data
}
// 通道只能用于接收数据的函数(只读)
func receiveData(ch <-chan int) {
    data := <-ch
    fmt.Println("Received", data)
}
func main() {
    // 创建一个双向通道
    ch := make(chan int)
    // 将通道通过不同函数的参数限制为发送或接收操作
    go sendData(ch, 10)
    go receiveData(ch)
    // 等待goroutine执行完毕后打印输入的内容
    var input string
    fmt.Scanln(&input)
    fmt.Println(input)
}通道选择器
Go 的 选择器(select) 让你可以同时等待多个通道操作。 将协程、通道和选择器结合,是 Go 的一个强大特性。
package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now()
    // 创建两个通道,用于模拟两个并发的任务
    ch1 := make(chan string)
    ch2 := make(chan string)
    // 启动第一个并发任务
    go func() {
        time.Sleep(2 * time.Second) // 模拟任务执行时间
        ch1 <- "任务1完成"              // 将任务1的结果发送到通道ch1
    }()
    // 启动第二个并发任务
    go func() {
        time.Sleep(1 * time.Second) // 模拟任务执行时间
        ch2 <- "任务2完成"              // 将任务2的结果发送到通道ch2
    }()
    // 使用通道选择器选择首先到达的任务
    for i := 0; i < 2; i++ {
        select {
        case result := <-ch1: // 从通道ch1接收结果
            fmt.Println("ch1接收到结果:", result)
        case result := <-ch2: // 从通道ch2接收结果
            fmt.Println("ch2接收到结果:", result)
        }
    }
    // 主函数继续执行其他操作
    useTime := time.Since(now)
    fmt.Println("程序运行时间:", useTime)
}超时处理
package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now()
    // 创建一个通道,用于接收结果
    ch := make(chan string)
    // 启动一个并发任务
    go func() {
        // 模拟任务执行时间,这里设置为3秒
        time.Sleep(3 * time.Second)
        // 将任务结果发送到通道中
        ch <- "任务完成"
    }()
    // 使用 select 语句等待任务结果,设置超时时间为2秒
    select {
    case result := <-ch: // 如果在2秒内接收到了任务结果
        fmt.Println(result)
    case <-time.After(2 * time.Second): // 如果超过了2秒仍未接收到任务结果
        fmt.Println("任务超时")
    }
    // 主函数继续执行其他操作
    fmt.Println("程序运行时间:", time.Since(now))
    fmt.Println("主函数继续执行...")
}非阻塞通道操作
常规的通过通道发送和接收数据是阻塞的。 然而,我们可以使用带一个 default 子句的 select 来实现 非阻塞 的发送、接收,甚至是非阻塞的多路 select。
package main
import (
    "fmt"
    "time"
)
func main() {
    // 创建一个字符串通道
    ch := make(chan string)
    // 启动一个并发任务,向通道发送数据
    go func() {
        time.Sleep(2 * time.Second) // 模拟任务执行时间
        ch <- "任务完成"                // 将任务结果发送到通道中
    }()
    // time.Sleep(3 * time.Second)
    // 使用 select 语句进行非阻塞通道操作
    select {
    case result := <-ch: // 尝试从通道中接收数据
        fmt.Println(result)
    default:
        fmt.Println("暂无任务结果")
    }
    // 主函数继续执行其他操作
    fmt.Println("主函数继续执行...")
}通道的关闭
关闭 一个通道意味着不能再向这个通道发送值了。 该特性可以向通道的接收方传达工作已经完成的信息。
package main
import (
    "fmt"
)
func main() {
    // 创建一个整型通道
    ch := make(chan int)
    // 启动一个并发任务,向通道发送数据
    go func() {
        for i := 1; i <= 5; i++ {
            fmt.Println("发送数据:", i)
            ch <- i // 将数据发送到通道中
        }
        // 发送完数据后关闭通道
        close(ch)
        fmt.Println("数据发送完毕,通道关闭")
    }()
    // 从通道中循环接收数据,直到通道关闭
    for {
        // 尝试从通道中接收数据
        num, ok := <-ch
        // 如果通道已关闭并且没有数据可接收,则退出循环
        if !ok {
            fmt.Println("通道已关闭,没有数据可接收")
            break
        }
        // 打印接收到的数据
        fmt.Println("接收到数据:", num)
    }
    // 主函数继续执行其他操作
    fmt.Println("主函数继续执行...")
}通道遍历
在前面的章节我们讲过 for 和 range 为基本的数据结构提供了迭代的功能。 我们也可以使用这个语法来遍历的从通道中取值。
package main
import (
    "fmt"
)
func main() {
    // 创建一个整型通道
    ch := make(chan int)
    // 启动一个并发任务,向通道发送数据
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i // 将数据发送到通道中
        }
        // 关闭通道
        close(ch)
    }()
    // 使用 for 循环遍历通道,接收数据并打印
    for num := range ch {
        fmt.Println("接收到数据:", num)
    }
    // 主函数继续执行其他操作
    fmt.Println("主函数继续执行...")
}