Go并发学习

  • 参考:

https://docs.microsoft.com/zh-cn/learn/modules/go-concurrency/0-introduction

概念词

  • channel (通道)是处理并发程序间的通信的。
  • 我们不必通过共享内存来通信”的原因。 相反,应该通过通信共享内存。
  • 无缓冲 channel 同步通信。 对于发送的每一条数据,都需要有一种接收消息的方法。 否则,程序将被阻止。

goroutine(轻量级线程)

channel 管道

go中的channel是goroutine之间的通信机制,这就是为什么我们之前说过go实现并发的方式是:"不是通过共享内存通信,而是通过通信共享内存。"需要将值从一个goroutine发送到另一个时,可以使用通道。

  • channel 有两种类型: 无缓冲channel、有缓存channel。
  • channel
  • 同步通信使用无缓冲channel,保证每次发送数据时,程序都会被阻塞,等待从channel中读取数据后在,依次循环进行。
  • 相反,有缓存channel将发送和接收操作解耦。它们不会阻塞程序,但必须小心使用,因为可能最终会导致死锁。

由于channel是发送和接收数据的通信机制,因此它也有类型之分。这意味着你只能发送channel支持的数据类型。除使用关键字chan作为channel的数据类型外,还需指定将通过channel传递的数据类型,如果int类型。

  • channel 有传递数据类型。如int
  • 创建channel时需要使用内置函数 make来创建例如: ch := make(chan int)
  • 一个channel 可以执行两项操作:发送数据和接收数据。
  • 若要指定channel具有的操作类型,需要使用channel运算符<-
  • 在channel中发送数据和接收数据属于阻止操作。
  • 如果需要channel仅发送数据,则必须在channel之后使用<-运算符。
  • 如果希望channel仅接收数据,则必须在channel之前使用<-运算符。
  • 我们建议在使用 channel 时始终使用 goroutine。
ch <- x // sends (or write) x through channel ch  通过通道ch 发送或写入(send or write) 变量x的内容。
x = <-ch // x receives (or reads) data sent to the channel ch  接收或读取通道ch内的数据并赋值给变量x。
<-ch // receives data, but the result is discarded  接收ch的数据,但结果丢弃。
  • 可在channel中执行的另一项操作是关闭channel。要关闭channel,需使用内置的close()函数如: close(ch)
  • 关闭channel时,希望数据不在该channel中发送,如果试图将数据发送给一个已关闭的channel,则程序将发生严重错误。如果试图通过已关闭的channel接收数据,则可以读取发送的所有数据。随后的每次“读取”都将返回一个零值。

通俗理解上一段话

  1. 如果在一个已关闭的channel发送数据,则程序会报严重错误。
  2. 如果在一个已关闭的channel接收数据,则第一次可以读取发送的所有数据,在往后读取则都是返回零值。也就是说关闭的channel会保留所有发送过的数据,当在关闭后第一次接收完成后 清空或者。

无缓冲channel

在使用make()函数创建channel时,会创建一个无缓冲channel,这是默认行为。无缓冲channel会阻止发送操作,直到有人准备好接收数据。这就是为什么我们之前说发送和接收都属于阻止操作。

  • 无缓冲channel是阻塞的。
  • 无缓冲 channel 在同步发送和接收操作。 即使使用并发,通信也是同步的。
  • 无缓冲channel是同步阻塞的,他必须存在接收操作时,它们才接受发送操作。否则程序将永久阻塞等待。就是创建了多个发送数据,就要有相同数量的接收数据,当接收数据比发送数据大是,由于接收数据无法获取多余的发送数据返回,则将永久阻塞程序。如果发送数据大于接收数据数量时,则很可能无法保证多出的go协程 能够顺利执行完就被打断。

有缓冲channel

正如你所了解的,默认情况下 channel 是无缓冲行为。 这意味着只有存在接收操作时,它们才接受发送操作。 否则,程序将永久被阻止等待。
有时需要在 goroutine 之间进行此类同步。 但是,有时你可能只需要实现并发,而不需要限制 goroutine 之间的通信方式。
有缓冲 channel 在不阻止程序的情况下发送和接收数据,因为有缓冲 channel 的行为类似于队列。 创建 channel 时,可以限制此队列的大小,如下所示:

ch := make(chan string,10)

有缓存vs无缓冲channel

现在,你可能想知道何时使用这两种类型。 这完全取决于你希望 goroutine 之间的通信如何进行。 无缓冲 channel 同步通信。 它们保证每次发送数据时,程序都会被阻止,直到有人从 channel 中读取数据。

相反,有缓冲 channel 将发送和接收操作解耦。 它们不会阻止程序,但你必须小心使用,因为可能最终会导致死锁(如前文所述)。 使用无缓冲 channel 时,可以控制可并发运行的 goroutine 的数量。 例如,你可能要对 API 进行调用,并且想要控制每秒执行的调用次数。 否则,你可能会被阻止。

声明channel方向

Go 中 channel 的一个有趣特性是,在使用 channel 作为函数的参数时,可以指定 channel 是要发送数据还是接收数据。 随着程序的增长,可能会使用大量的函数,这时候,最好记录每个 channel 的意图,以便正确使用它们。 或者,你要编写一个库,并希望将 channel 公开为只读,以保持数据一致性。

  • 调用协程函数传参channel时可以 声明是发送数据func send(ch chan<- string,message string) 还是接收数据func read(ch <-chan string)
package main

import "fmt"

//发送&写入数据 一旦被声明则只能写入 不能有读取在函数体内
func send(ch chan<- string, message string) {
    fmt.Printf("Sending: %#v\n", message)
    ch <- message
}

//接收&读取数据 一旦被声明则只能读取 不能有发送在函数体内
func read(ch <-chan string) {
    fmt.Printf("Receiving: %#v\n", <-ch)
}

func main() {
    ch := make(chan string, 1)
    send(ch, "Hello World!")
    read(ch)
}

#输出
Sending: "Hello World!"
Receiving: "Hello World!"

多路复用

最后,让我们讨论一个关于如何在使用 select 关键字的同时与多个 channel 交互的简短主题。 有时,在使用多个 channel 时,需要等待事件发生。 例如,当程序正在处理的数据中出现异常时,可以包含一些逻辑来取消操作。
select 语句的工作方式类似于 switch 语句,但它适用于 channel。 它会阻止程序的执行,直到它收到要处理的事件。 如果它收到多个事件,则会随机选择一个。
select 语句的一个重要方面是,它在处理事件后完成执行。 如果要等待更多事件发生,则可能需要使用循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值