Go 语言中的并发特性

开发 后端
go 在并发方面为我们提供了一个语言级别的支持, goroutine 和 chan 相互配合,这决定了他的先天优势。

[[411568]]

本节源码位置 https://github.com/golang-minibear2333/golang/blob/master/4.concurrent/4.1-goroutine/”

以前我们写并发的程序一般是用多线程来实现,自己维护一个线程池,在恰当的时候创建、销毁、分配资源。

go 在并发方面为我们提供了一个语言级别的支持, goroutine 和 chan 相互配合,这决定了他的先天优势。

goroutine 的概念类似于线程, Go 程序运行时会自动调度和管理,系统能智能地将 goroutine 中的任务合理地分配给 CPU , 让这些任务尽量并发运作。

他和线程对比

从使用上讲

  • 比线程更轻量级,可以创建十万、百万不用担心资源问题。
  • 和 chan 搭配使用,实现高并发, goroutine 之间传输数据更方便。
  • 如果访问同一个数据块,要小心数据竞态问题、共享锁还是互斥锁的选择问题、并发操作的数据同步问题(后面会说)

从其实现上讲

  • 从资源上讲,线程的栈内存大小一般是固定的一般为 2MB ,虽然这个数值可以设置,但是 太大了浪费,太小了容易不够用, 而 goroutine 栈内存是可变的,初始一般为 2KB ,随着需求可以扩大达到 1GB。所以 goroutine 十分的轻量级,且能满足不同的需求。
  • 从调度上讲,线程的调度由 OS 的内核完成;线程的切换需要 CPU 寄存器 和 内存的数据交换 ,从而切换不同的线程上下文。其触发方式为 CPU时钟 , 而 goroutine 的调度则比较轻量级,由自身的调度器完成。
  • 协程同线程的关系,有些类似于 线程同进程的关系。

创建与使用

创建一个 goroutine ,只需要在函数前加一个 go 关键字就成了。

  1. go 函数名(参数) 

看一个 dome

  1. func quickFun(){ 
  2.  fmt.Println("maybe you can's see me!"
  3.  
  4. func main(){ 
  5.  go quickFun() // 创建了一个 goroutine 
  6.  fmt.Println("hey"
  7.  time.Sleep(time.Second
  • goroutine 和 main 主线程同时运行
  • main 运行结束会暴力终止所有协程,所以上面的程序多等待了 1 秒
  • Go 程序从 main 包的 main() 函数开始,在程序启动时, Go 程序就会为 main() 函数创建一个默认的 goroutine 。

输出

  1. hey 
  2. maybe you can's see me! 

对,就是这么简单,如果你的函数只在这里使用,也可以用匿名函数来创建 goroutine 。

  1. func main(){ 
  2.  go func() { 
  3.   fmt.Println("hello "
  4.  }() 
  5.  time.Sleep(time.Second) //main运行结束会暴力终止所有协程,所以这里先等待1秒 

PS: 和线程不同,goroutine没有唯一的id,所以我们没办法专门q针对某个协程进行操作。

goroutine

goroutine 是 Go 语言并行设计的核心。goroutine 是一种比线程更轻量的实现,十几个 goroutine 可能在底层就是几个线程。实际上是 Go 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理。

使用 goroutine 只需要简单的在需要执行的函数前添加 go 关键字即可。当执行 goroutine 时候,Go 语言立即返回,接着执行剩余的代码,不会阻塞主线程。

下面我们通过一小段代码来讲解 go 的使用:

  1. //首先我们先实现一个 Add()函数 
  2. func Add(a, b int) { 
  3. c := a + b 
  4. fmt.Println(c) 
  5.  
  6. go Add(1, 2) //使用go关键字让函数并发执行 

Go 的并发执行就是这么简单,当在一个函数前加上 go 关键字,该函数就会在一个新的 goroutine 中并发执行,当该函数执行完毕时,这个新的 goroutine 也就结束了。不过需要注意的是,如果该函数具有返回值,那么返回值会被丢弃。所以什么时候用 go 还需要酌情考虑。

接着我们通过一个案例来体验一下 Go 的并发到底是怎么样的。新建源文件 goroutine2.go,输入以下代码:

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. func Add(a, b int) { 
  6.  c := a + b 
  7.  fmt.Println(c) 
  8.  
  9. func main() { 
  10.  for i := 0; i < 10; i++ { 
  11.   go Add(i, i) 
  12.  } 

执行 goroutine.go 文件会发现屏幕上什么都没有,但程序并不会报错,这是什么原因呢?

原来当主程序执行到 for 循环时启动了 10 个 goroutine,然后主程序就退出了,而启动的 10 个 goroutine 还没来得及执行 Add() 函数,所以程序不会有任何输出。也就是说主 goroutine 并不会等待其他 goroutine 执行结束。

Go 语言提供的信道(channel)就是专门解决并发通信问题的,下一节我们将详细介绍。

小结

学 go 语言必学并发,通过本节我们知道了协程是非常容易创建的,而且他非常轻量只占用 4k,其他语言很容易就上MB

协程的使用还要配合数据传输,生产者消费者模型,关于协程的调度,我们后续再说。

 

另外并发 bug 的定位和解决是老大难的问题了,平时就要注意的良好的代码风格和编程习惯。

本文转载自微信公众号「机智的程序员小熊」,可以通过以下二维码关注。转载本文请联系机智的程序员小熊公众号。

 

责任编辑:武晓燕 来源: 机智的程序员小熊
相关推荐

2023-12-21 07:09:32

Go语言任务

2023-01-30 15:41:10

Channel控制并发

2014-04-09 09:32:24

Go并发

2017-06-27 08:54:59

2024-04-07 11:33:02

Go逃逸分析

2022-07-19 12:25:29

Go

2023-11-30 08:09:02

Go语言

2023-07-29 15:03:29

2021-06-08 07:45:44

Go语言优化

2023-01-12 08:52:50

GoroutinesGo语言

2013-05-28 09:43:38

GoGo语言并发模式

2023-12-25 09:58:25

sync包Go编程

2024-05-10 08:36:40

Go语言对象

2012-06-15 09:56:40

2021-07-30 07:28:15

WorkerPoolGo语言

2021-07-13 06:44:04

Go语言数组

2024-01-08 07:02:48

数据设计模式

2023-12-30 18:35:37

Go识别应用程序

2023-11-21 15:46:13

Go内存泄漏

2024-03-26 11:54:35

编程抽象代码
点赞
收藏

51CTO技术栈公众号