go 语言中的并发特性

简介: go 语言中的并发特性

本节源码位置 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 关键字就成了。


go 函数名(参数)



看一个 dome

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


输出


hey
maybe you can's see me!


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

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

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


goroutine


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


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


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

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

Go 的并发执行就是这么简单,当在一个函数前加上 go 关键字,该函数就会在一个新的 goroutine 中并发执行,当该函数执行完毕时,这个新的 goroutine

也就结束了。不过需要注意的是,如果该函数具有返回值,那么返回值会被丢弃。所以什么时候用 go 还需要酌情考虑。


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

package main
import "fmt"
func Add(a, b int) {
  c := a + b
  fmt.Println(c)
}
func main() {
  for i := 0; i < 10; i++ {
    go Add(i, i)
  }
}


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


原来当主程序执行到 for 循环时启动了 10 个 goroutine,然后主程序就退出了,而启动的 10 个 goroutine 还没来得及执行

Add() 函数,所以程序不会有任何输出。也就是说主 goroutine 并不会等待其他 goroutine 执行结束。


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


小结


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


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


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

相关文章
|
5天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
3天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
11 2
|
3天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
14 2
|
6天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
3天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
10 4
|
3天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
14 1
|
7天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
7天前
|
Go 数据处理 调度
探索Go语言的并发模型:Goroutines与Channels的协同工作
在现代编程语言中,Go语言以其独特的并发模型脱颖而出。本文将深入探讨Go语言中的Goroutines和Channels,这两种机制如何协同工作以实现高效的并发处理。我们将通过实际代码示例,展示如何在Go程序中创建和管理Goroutines,以及如何使用Channels进行Goroutines之间的通信。此外,本文还将讨论在使用这些并发工具时可能遇到的常见问题及其解决方案,旨在为Go语言开发者提供一个全面的并发编程指南。
|
5天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
16天前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
32 3