Golang 语言中 Channel 的使用方式

简介: Golang 语言中 Channel 的使用方式

介绍

在「Effective Go」并发章节讲到,“不要通过共享内存进行通信;而是通过通信共享内存。由此表明 Golang 语言官方鼓励用户使用“通过通信共享内存”的方式并发编程。

但是,Golang 语言也在标准库 sync 包中提供了传统的同步原语。

我们应该选择哪种并发编程方式呢?Golang 语言官方也给了使用指南:

Channel Mutex
passing ownership of data,
distributing units of work,
communicating async results
caches,
state

如表格所示,传递数据的所有权,分发工作任务和通信异步结果,这三种场景建议使用 Channel。缓存和状态,这两种场景建议使用 Mutex。

在 Golang 语言官方博客「The Go Memory Model」一文中,对 channel 的描述如下:

A send on a channel happens before the corresponding receive from that channel completes.

A receive from an unbuffered channel happens before the send on that channel completes.

本文我们主要介绍一些关于 Channel 的使用方式。关于 Golang 语言 sync 包的同步原语和 Channel 的介绍,我们在之前的文章已经介绍过了,在此不再赘述,感兴趣的读者朋友可以按需翻阅。

02

无缓冲 channel

无缓冲 channel 可用于两个 goroutine 之间传递信号,比如以下示例:

顺序打印 1 至 100 的奇数和偶数:

func main () {
 block := make(chan struct{})
 go odd(block)
 go even(block)
 time.Sleep(time.Second)
 fmt.Println("done")
}
func odd (block chan struct{}) {
 for i := 1; i <= 100; i++ {
  <-block
  if i % 2 == 1 {
   fmt.Println("奇数:", i)
  }
 }
}
func even (block chan struct{}) {
 for i := 1; i <= 100; i++ {
  block <- struct{}{}
  if i % 2 == 0 {
   fmt.Println("偶数:", i)
  }
 }
}

阅读上面这段代码,我们使用一个无缓冲 channel 作为两个 goroutine 之间的信号传递的桥梁。

主子 goroutine 之间传递信号:

func main () {
 block := make(chan struct{})
 go func() {
  for i := 0; i < 10; i++ {
   fmt.Println(i)
  }
   // block <- struct{}{}
    close(block)
 }()
 <-block
 fmt.Println("done")
}

阅读上面这段代码,我们使用一个无缓冲 channel 作为主子 goroutine 之间的信号传递的桥梁。通过信号传递,主 goroutine 在子 goroutine 运行结束之后再退出。

03

有缓冲 channel

有缓冲 channel 可以用于解耦操作,模拟消息队列。“生产者”和“消费者”只需各自处理 channel,实现解耦。

解耦“生产者”和“消费者”:

func main () {
 task := make(chan int, 10)
 go consumer(task)
 // 生产者
 for i := 0; i < 10; i++ {
  task <- i
 }
 time.Sleep(time.Second * 2)
}
// 消费者
func consumer (task <-chan int) {
 for i := 0; i < 10; i++ {
  go func(id int) {
   t := <- task
   fmt.Println(id, t)
  }(i)
 }
}

阅读上面这段代码,我们使用一个有缓冲的 channel,将“生产者”和“消费者”做解耦操作。

04

超时操作和定时器

我们还可以通过 select 和 channel,实现超时操作和定时器。

超时操作:

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")
    }
}

阅读上面这段代码,我们通过 c1 和 c2 两个 channel,分别模拟出超时和未超时场景。

定时器:

func main() {
    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)
    go func() {
        for {
            select {
            case <-done:
                return
            case t := <-ticker.C:
                fmt.Println("Tick at", t)
            }
        }
    }()
    time.Sleep(1600 * time.Millisecond)
    ticker.Stop()
    done <- true
    fmt.Println("Ticker stopped")
}

阅读上面这段代码,我们定义一个打点器,每间隔 500ms 执行一次操作,当打点器 stop 时,通过一个无缓冲 channel 传递退出信号。

05

总结

本文我们介绍了一些关于 Channel 的使用方式,我们在阅读完本文后可以了解无缓冲 channel 作为信号传递的使用方式和有缓冲 channel 解耦操作的方式,以及 channel 与 select 配合使用的用法。

推荐阅读:

参考资料:

https://golang.org/doc/effective_go#concurrency 

https://github.com/golang/go/wiki/MutexOrChannel 

https://tip.golang.org/ref/mem#tmp_3 

https://gobyexample.com/timeouts 

https://gobyexample.com/tickers 


目录
相关文章
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
110 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
66 4
Golang语言文件操作快速入门篇
|
2月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
101 3
Golang语言之gRPC程序设计示例
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
86 4
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
50 4
Golang语言goroutine协程篇
|
2月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
51 3
Golang语言之Prometheus的日志模块使用案例
|
1月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
30 0
|
2月前
|
JSON Go 数据格式
Golang语言结构体链式编程与JSON序列化
这篇文章是关于Go语言中结构体链式编程与JSON序列化的教程,详细介绍了JSON格式的基本概念、结构体的序列化与反序列化、结构体标签的使用以及如何实现链式编程。
38 4
|
2月前
|
Go
Golang语言结构体(struct)面向对象编程进阶篇(封装,继承和多态)
这篇文章是关于Go语言中结构体(struct)面向对象编程进阶篇的教程,涵盖了Go语言如何实现封装、继承和多态,以及结构体内存布局的相关概念和案例。
143 4
|
2月前
|
Go
Golang语言基础之接口(interface)及类型断言
这篇文章是关于Go语言中接口(interface)及类型断言的详细教程,涵盖了接口的概念、定义、实现、使用注意事项以及类型断言的多种场景和方法。
37 4