侧边栏壁纸
博主头像
爪一哇 ITBoy博主等级

面向加薪学习

  • 累计撰写 2 篇文章
  • 累计创建 5 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
Go

Golang通道

fanxiaofan
2023-12-10 / 0 评论 / 0 点赞 / 140 阅读 / 9742 字 / 正在检测是否收录...

一、什么是通道

Golang 中通道是一种安全、灵活和高效的并发机制,用于在并发环境下实现数据的同步和传递。通道提供了一个线程安全的队列,只允许一个 goroutine 进行读操作,另一个 goroutine 进行写操作。通过这种方式,通道可以有效地解决并发编程中的竞态条件、锁问题等常见问题。

通道有两种类型:有缓冲通道和无缓冲通道。在通道创建时,可以指定通道的容量,即通道缓冲区的大小,如果不指定则默认为无缓冲通道。

二、通道的基本语法

Golang创建最通道的基本语法,使用 make创建,基本语法如下:

ch := make(chan Type,[buffer])

使用 make内置函数 创建通道,chan 关键字定义通道。Type为存储在通道中的数据类型。buffer为可选项表示通道的缓冲区大小,不填写或 0即表示为无缓冲区。其中读写通道数据使用<-。例如:

ch := make(chan int)
ch <- 7 //写入 通道
a := <-ch //读取 通道

三、无缓冲通道

无缓冲区的通道,需要一个读取和一个写入。如果没有读取只有写入,写入的线程将会被阻。如果只有读取没有写入这个通道将没有意义。

package main
import (
    "time"
    "fmt"
)

func write(ch chan int){
    ch <- 7
    fmt.Printf("%d:写入 %d\n",time.Now().Unix,7)
}

func read(ch chan int){
    a := <-ch
    fmt.Printf("%d:读取 %d",time.Now().Unix,a)
}

func main(){
    go write()

    time.Sleep(1*time.Second)

    go read()

    time.Sleep(1.time.Second)
}

执行效果如下:

tiancaifan@VM-16-2-debian:/home/Go_Code# go run ch.go 
4705472:读取7
4705472:写入7
tiancaifan@VM-16-2-debian:/home/Go_Code# 

当我们将20 行和 22 行注释以后执行没有任何输出,效果如下:

tiancaifan@VM-16-2-debian:/home/Go_Code# go run ch.go 
tiancaifan@VM-16-2-debian:/home/Go_Code# 

四、有缓冲通道

在 Golang 中,通道还支持缓冲机制。通道的缓冲区可以存储一定量的数据,当缓冲区满时,向通道写入数据将阻塞。当通道缓冲区为空时,从通道读取数据将阻塞。

缓冲区大小为 0 的通道称为无缓冲通道。无缓冲通道的发送和接收操作都是阻塞的,因此必须有接收者准备好接收才能进行发送操作。这种机制确保了通道的同步性,即在通道操作前后,发送者和接收者都会被阻塞,直到双方方做好准备。

 ch := make(chan int, 5)

这行代码创建了一个名为 ch 的通道,通道的数据类型为 int,通道缓冲区的大小为 5。向有缓冲通道写入数据时,如果缓冲区未满,则写操作将成功,程序将继续执行。如果缓冲区已满,则写操作将阻塞,直到有空闲缓冲区可用。

从有缓冲通道读取数据时,如果缓冲区不为空,则读操作将成功,程序将继续执行。如果缓冲区为空,则读操作将阻塞,直到有数据可读取。

五、Golang通道的超时和计时器

在并发编程中,常常需要对通道进行超时和计时操作。Golang 中提供了 time 包来实现超时和计时器。

5.1 超时机制

在 Golang 中,可以使用 select 语句和 time.After 函数来实现通道的超时操作。例如:

select {
  case data := <-ch:
    fmt.Println(data)
  case <-time.After(time.Second):
    fmt.Println("timeout")
}

这段代码中,select 语句监听了通道 ch 和 time.After(time.Second) 两个信道,如果 ch 中有数据可读,则读取并输出数据;如果等待 1 秒钟后仍然没有数据,则超时并输出 timeout。

5.2 计时器机制

Golang 中提供了 time 包来实现计时器机制。可以使用 time.NewTimer(duration) 函数创建一个计时器,计时器会在 duration 时间后触发一个定时事件。例如:

timer := time.NewTimer(time.Second * 2)
<-timer.C
fmt.Println("Timer expired")

这段代码创建了一个计时器,设定时间为 2 秒钟,当计时器到达 2 秒钟时,会向 timer.C 信道中发送一个定时事件,程序通过 <-timer.C 语句等待定时事件的到来,并在接收到定时事件后输出 “Timer expired”。

六、关闭通道

在 Golang 中使用 close 函数来关闭通道。关闭通道后,通道的读写操作将会失败,读取通道将会得到零值,写入通道将会导致 panic 异常。例如:

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

在上述代码中,我们创建了一个名为 ch 的通道,向在一个 goroutine 中向通道中写入数据 0 到 9,并通过 close 函数关闭通道。在主 goroutine 中,通过 for range 语句循环读取通道中的数据,输出到控制台中。当通道被关闭时,for range 语句将会自动退出循环。

当通道关闭以后,仍然可以从通道中读取已经存在的数据,例如:

ch := make(chan int)
go func() {
  for i := 0; i < 10; i++ {
    ch <- i
  }
  close(ch)
}()
for {
  data, success := <-ch
  if !success {
    break
  }
  fmt.Println(data)
}

在上述代码中,我们通过循环读取通道中的数据,判断通道是否已经被关闭,当通道被关闭时,读取操作将会失败,其中success 的值将会变为 false,然后退出循环。

七、常见应用

通道是 Golang 并发编程中的重要组成部分,其常见的应用场景包括:

7.1 同步数据传输

通道可以被用来在不同的 goroutine 之间同步数据。当一个 goroutine 需要等待另一个goroutine 的结果时,可以使用通道进行数据的传递。例如:

package main

import "fmt"

func add(a, b int, data chan int) {
  data <- a + b
}

func main() {
  data := make(chan int)
  go add(10, 20, data)
  fmt.Println(<-data)
}

在上述代码中,我们使用通道进行a与b的加法运算,将结果返回至通道中,在主函数中我们等待通道返回结果。

7.2 协调多个 goroutine

通道也可以用于协调多个 goroutine 之间的操作。例如,在一个生产者-消费者模式中,通道可以作为生产者和消费者之间的缓冲区,协调数据的生产和消费。例如:

package main

import (
  "fmt"
  "sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
  for j := range jobs {
    fmt.Println("worker", id, "processing job", j)
    results <- j * 2
  }
}

func main() {
  jobs := make(chan int, 100)
  results := make(chan int, 100)

  // 开启三个worker goroutine
  for w := 1; w <= 3; w++ {
    go worker(w, jobs, results)
  }

  // 发送9个任务到jobs通道中
  for j := 1; j <= 9; j++ {
    jobs <- j
  }
  close(jobs)

  // 输出每个任务的结果
  for a := 1; a <= 9; a++ {
    <-results
  }
}

在这个例子中,我们使用通道来协调三个 worker goroutine 之间的任务处理。每个 worker goroutine 从 jobs 通道中获取任务,并将处理结果发送到 results 通道中。主函数负责将所有任务发送到 jobs 通道中,并等待所有任务的结果返回。

八、总结

通道是 Go 中非常重要的并发原语,可以有效地管理并发访问共享数据,避免数据竞争。通过通道,可以实现同步和异步的消息传递,实现不同 goroutine 之间的通信。在使用通道时,需要注意通道的基本语法、缓冲机制、超时和计时器、通道的传递、单向通道和关闭通道等知识点,并根据实际场景选择合适的通道模式,以提高程序的并发性能和稳定性

0
  • ${post.likes!0}

评论区