Go 并发和并行/协程/信道/缓冲信道

并发

  Go 是并发式语言,而不是并行式语言。在讨论 Go 如何处理并发之前,我们必须理解何为并发,以及并发与并行的区别。

并发是什么?

  并发是指立即处理多个任务的能力。一个CPU的情况下<意指看上去像是同时运行,其中有io的阻塞态等待的时间慢而已。

例子1:

我们可以想象一个人正在跑步。假如在他晨跑时,鞋带突然松了。于是他停下来,系一下鞋带,接下来继续跑。这个例子就是典型的并发。这个人能够一下搞定跑步和系鞋带两件事,即立即处理多个任务。

例子2:

       顺序执行:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
       并发:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
       并行:你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并

理解:

解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群

并行是什么?并行和并发有何区别?

  并行是指同时处理多个任务。这听起来和并发差不多,但其实完全不同。

我们同样用这个跑步的例子来帮助理解。假如这个人在慢跑时,还在用他的 iPod 听着音乐。在这里,他是在跑步的同时听音乐,也就是同时处理多个任务。这称之为并行。

Go 对并发的支持

  Go 编程语言原生支持并发。Go 使用 Go 协程(Goroutine) 和信道(Channel)来处理并发。在接下来的教程里,我们还会详细介绍它们。

Go 协程是什么?

  Go 协程是与其他函数或方法一起并发运行的函数或方法。Go 协程可以看作是轻量级线程。与线程相比,创建一个 Go 协程的成本很小。因此在 Go 应用中,常常会看到有数以千计的 Go 协程并发地运行。

Go 协程相比于线程的优势

  • 相比线程而言,Go 协程的成本极低。堆栈大小只有若干 kb,并且可以根据应用的需求进行增减。而线程必须指定堆栈的大小,其堆栈是固定不变的。
  • Go 协程会复用(Multiplex)数量更少的 OS 线程。即使程序有数以千计的 Go 协程,也可能只有一个线程。如果该线程中的某一 Go 协程发生了阻塞(比如说等待用户输入),那么系统会再创建一个 OS 线程,并把其余 Go 协程都移动到这个新的 OS 线程。所有这一切都在运行时进行,作为程序员,我们没有直接面临这些复杂的细节,而是有一个简洁的 API 来处理并发。
  • Go 协程使用信道(Channel)来进行通信。信道用于防止多个协程访问共享内存时发生竞态条件(Race Condition)。信道可以看作是 Go 协程之间通信的管道。我们会在下一教程详细讨论信道。

如何启动一个 Go 协程?

  调用函数或者方法时,在前面加上关键字 go,可以让一个新的 Go 协程并发地运行。

例子:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. // go协程
  7. func index() {
  8. fmt.Println("hello world")
  9. }
  10. func main() {
  11. go index()
  12. go index()
  13. go index()
  14. go index()
  15. fmt.Println("")
  16. time.Sleep(time.Second*3)
  17. }

  协程的书写直接在函数前加关键字“go”即可开启协程,运行是从上至下,遇到io阻塞态时等待自行的切换处理。各个协程之间做数据的通信,开多个协程就是这么简单的实现。

time模块的时间传参,点击second查看源码:

总结:

  • 启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。
  • 如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。

信道(channel)

什么是信道?

  信道可以想像成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收。

信道的声明

  所有信道都关联了一个类型。信道只能运输这种类型的数据,而运输其他类型的数据都是非法的。

chan T 表示 T 类型的信道。信道是一个值类型,可以作为参数传递。

信道的零值为 nil。信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make 来定义信道。

下面编写代码,声明一个信道。

 
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. //信道、管道 通道 channel
  7. //
  8. func main() {
  9. // //信道也是个变量
  10. // //信道运输的类型是int类型
  11. // //空值 是nil 引用类型
  12. // var a chan int
  13. // fmt.Println(a)
  14. // //初始化
  15. var a chan int =make(chan int)
  16. fmt.Println(a)
  17. // //重点 :放值和取值
  18. // //箭头向信道变量,就是往信道中放值
  19. a <-1
  20. // //箭头向外,表示从信道中取值
  21. <-a
  22. // //信道默认情况下,取值和赋值都是阻塞的 ******重要****
  23. //
  24. }

简短声明通常也是一种定义信道的简洁有效的方法。

 
  1. a := make(chan int)

这一行代码同样定义了一个 int 类型的信道 a,类型可以是其他数据结构bool string int...

通过信道进行发送和接收

  如下所示,该语法通过信道发送和接收数据。

 
  1. data := <- a // 读取信道 a
  2. a <- data // 写入信道 a

信道旁的箭头方向指定了是发送数据还是接收数据。

在第一行,箭头对于 a 来说是向外指的,因此我们读取了信道 a 的值,并把该值存储到变量 data

在第二行,箭头指向了 a,因此我们在把数据写入信道 a

 
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. // 信道,管道,通道 channel
  7. func main() {
  8. // 定义信道
  9. // 信道也是个变量
  10. // 控制nil 引用的类型时int
  11. //var a chan int
  12. //fmt.Println(a)
  13. // 初始化
  14. var a chan int =make(chan int )
  15. fmt.Println(a)
  16. // 重点:放值和取值
  17. // 箭头向通道变量 就是往信道中值
  18. a<-1 // 阻塞态 不在往下运行 等待取值
  19. // 箭头向外,表示信道中取值
  20. b:=<-a
  21. fmt.Println(b)
  22. time.Sleep(time.Second*1)
  23. //信道默认情况下,取值和赋值都是阻塞的 ******重要****
  24. }

信道默认情况下,取值和赋值都是阻塞的,存和取都需要通过信道之间的通信进行传递参数,不然会造成死锁现象。

发送与接收默认是阻塞的

  发送与接收默认是阻塞的。这是什么意思?当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。与此类似,当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。

信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。

信道的代码示例

  接下来写点代码,看看协程之间通过信道是怎么通信的吧。

 
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. // go协程 信道之间的通信传递
  7. func index1(a chan bool) {
  8. fmt.Println("hello go")
  9. // 一旦执行完毕,往信道中存放一个值
  10. time.Sleep(time.Second*3)
  11. a<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值