瞅一眼就会使用GO的并发编程分享

简介: 瞅一眼就会使用GO的并发编程分享

GO的并发编程分享

之前我们分享了网络编程,今天我们来看看GO的并发编程分享,我们先来看看他是个啥

啥是并发编程呢?

指在一台处理器上同时处理多个任务

此处说的同时,可不是同一个时间一起手拉手做同一件事情

并发是在同一实体上的多个事件,而这个事件在同一时间间隔发生的,同一个时间段,有多个任务执行,可是同一个时间点,只有一个任务在执行

为啥要有并发编程?

随着互联网的普及,互联网用户人数原来越多,这对系统的性能带来了巨大的挑战。

我们要通过各种方式来高效利用硬件的性能(压榨),从而提高系统的性能进而提升用户体验,提升团队或者企业的竞争力。

并发是为了解决什么问题?目的是啥?

充分的利用好处理器的每一个核,以达到最高的处理性能,尽可能的运用好每一块砖

可是由于现在我们使用的CPU,内存,IO三者之间速度不尽相同

我们为了提高系统性能,计算机系统会将这三者速度进行平衡,以达到最优的效果,都有如下措施:

  • 操作系统增加了进程线程,以分时复用 CPU,进而均衡 CPUI/O 设备的速度差异;
  • CPU 增加了缓存,以均衡与内存的速度差异;
  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。

说到进程和线程,他们都是干啥的呢,咱们顺带说一下?

  • 进程是程序在操作系统中的一次执行过程

是 系统进行资源分配和调度的一个独立单位

  • 线程是进程的一个执行实体

是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

  • 一个进程可以创建和撤销多个线程, 并且同一个进程中的多个线程之间可以并发执行。

讲到并发编程不得不说并发和并行有啥区别?是不是总是有小伙伴弄不清楚他们到底是啥区别,好像一样,又好像不一样

并发和并行的区别

一言蔽之,区别如下:

并发

多线程程序在一个核的 CPU 上运行

并行

多线程程序在多个核的 CPU 上运行

并发就像多个小伙伴跑接力,同一个时间点只会有一个小伙伴在跑,互相有影响

并行就像是多个小伙伴同一个起点一起跑,互不干扰

我们需要记住一点,再强调一波:

并发不是并行

并发主要由切换时间片来实现"同时"运行

并行则是直接利用多核实现多线程的运行,

在 GO 可以设置使用核数,以发挥多核计算机的能力,不过设置核数都是依赖于硬件的

那么,讲到GO的并发编程,就必须上我们的主角,那就是协程

协程 goroutine 是啥?

协程是一种程序组件

是由子例程(过程、函数、例程、方法、子程序)的概念泛化而来的

子例程只有一个入口点且只返回一次,而协程允许多个入口点,可以在指定位置挂起和恢复执行。

协程和线程分别有啥特点嘞

  • 协程

独立的栈空间,共享堆空间,调度由用户自己控制

本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。

  • 线程

一个线程上可以跑多个协程,协程是轻量级的线程

GO 高并发的原因是啥?

  • goroutine 奉行通过通信来共享内存
  • 每个一个GO的实例有4~5KB的栈内存占用,并且由于 GO 实现机制而大幅减少的创建和销毁开销
  • Golang 在语言层面上就支持协程 goroutine

GOLANG并发编程涉及哪些知识点呢?

  • 基本协程的原理,实现方式,虽然说,GO中使用协程很方便,可以我们必须要知其然而值其所以然
  • Goroutine 池
  • runtime 包的使用
  • Channel 通道
  • 定时器
  • 并发且安全的锁
  • 原子操作
  • select 多路复用
  • 等等…

Goroutine的那些事

我们写C/C++的时候,我们必然也是要实现并发编程

我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换

且做线程池的时候,我们需要自己做一个线程管理的角色,灵活动态压缩和扩容

可是能不能有这样一种机制,我们只需要定义多个任务,让系统去帮助我们把这些任务分配到CPU上实现并发执行

GO里面就正好有这样的机制

goroutine 的概念类似于线程

goroutine 是由Go的运行时(runtime)调度和管理的

Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU

Go 在语言层面已经内置了调度和上下文切换的机制

写 GO 比较爽的一个地方是:

在GO里面,你不需要去自己写进程、线程、协程

我们可以使用 goroutine 包

如何使用 goroutine ?

我们需要让某个任务并发执行的时候,只需要把这个任务包装成一个函数

专门开启一个 goroutine 协程 去执行这个函数就可以了 , GO一个协程,很方便

一个 goroutine 必定对应一个函数,可以创建多个 goroutine 去执行相同的函数,只是多个协程都是做同一个事情罢了

我们先来使用一下协程,再来抛砖引玉,适当的分享一下

启动单个协程

func Hi() {
    fmt.Println("this is Hi Goroutine!")
}
func main() {
    Hi()
    fmt.Println("main goroutine!")
}

我们一般调用函数是如上这个样子的,效果如下

this is Hi Goroutine!
main goroutine!

其实我们调用协程的话,也与上述类似

我们可以使用 go 后面加上函数名字,来开辟一个协程,专门做函数需要执行的事情

func main() {
    go Hi() // 启动一个goroutine 协程 去执行 Hi 函数
    fmt.Println("main goroutine!")

实际效果我们可以看到,程序只打印了 main goroutine!

main goroutine!

在程序启动的时候,Go 程序就会为 main() 函数创建一个默认的 goroutine 协程

当 main() 函数返回的时候,刚开辟的另外一个 goroutine 协程 就结束了

所有在 main() 函数中启动的 goroutine 协程 会一同结束,老大死了,其余的傀儡也灰飞烟灭了

我们也可以让主协程等等一定子协程,待子协程处理完自己的事情,退出后,主协程再自己退出,这和我们写C/C++进程 和 线程的时候,类似

简单的,我们可以使用 time.sleep 函数来让主协程阻塞等待

我们也可以使用 上述提到的 使用 select{} 来达到目的

当然也有其他的方式,后续文章会慢慢的分享到

多个协程

那么多个协程又是怎么玩的呢?

我们使用 sync.WaitGroup 来实现goroutine 协程的同步

package main
import (
  "fmt"
  "sync"
)
var myWg sync.WaitGroup
func Hi(i int) {
  // goroutine 协程 结束就 记录 -1
  defer myWg.Done()
  fmt.Println("Hello Goroutine! the ", i)
}
func main() {
  for i := 0; i < 10; i++ {
    // 启动一个goroutine 协程 就记录 +1
    myWg.Add(1)
    go Hi(i)
  }
  // 等待所有记录 的goroutine 协程 都结束
  myWg.Wait() 
}

会有如下输出,每一个协程打印的数字并不是按照顺序来的:

Hello Goroutine! the  9
Hello Goroutine! the  4
Hello Goroutine! the  2
Hello Goroutine! the  3
Hello Goroutine! the  6
Hello Goroutine! the  5
Hello Goroutine! the  7
Hello Goroutine! the  8
Hello Goroutine! the  1
Hello Goroutine! the  0

还是同样的, 如果是主协程先退出,那么子协程还行继续运行吗?

毋庸置疑,主协程退出,子协程也会跟着退出

GO 中的 协程

分享如下几个点

GO中的栈是可增长的

一般都有固定的栈内存(通常为2MB),goroutine 的栈不是固定的,goroutine 的栈大小可以扩展到1GB

goroutine 是如何调度

这就不得不提 GPM

GPM是Go语言运行时(runtime)层面实现的,我们先简单了解一下GPM分别代表啥

G

就是个 goroutine ,里面除了存放本 goroutine 信息外 还有与所在P的绑定等信息

P

Processor 管理着一组 goroutine 队列

P 里面会存储当前 goroutine 运行的上下文环境(函数指针,堆栈地址及地址边界)

P 会对自己管理的 goroutine 队列做一些调度(比如把占用CPU时间较长的 goroutine 暂停、运行后续的 goroutine)

当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。

M(machine)

是 Go 运行时(runtime)对操作系统内核线程的虚拟

M 与内核线程一般是一一映射的关系, 一个 groutine 最终是要放到 M上执行

这里的 P 与 M 一般也是一一对应的

P 管理着一组G 挂载在 M 上运行

当一个 G 长久阻塞在一个 M 上时,runtime 会新建一个M,

阻塞 G 所在的 P 会把其他的 G 挂载在新建的M上

这个时候,当旧的 G 阻塞完成或者认为其已经挂了的话,就会回收旧的 M

还有一点

P 的个数是通过 runtime.GOMAXPROCS 设定(最大256),这个数字也依赖于自己的硬件,在并发量大的时候会增加一些 P 和 M ,但不会太多

总结

  • 分享了并发和并行
  • 分享了GO 的并发,协程的简单使用
  • 简单分享了GO可伸缩扩展的栈内存

关于GO的并发编程知识点涉及面多,对于他的调度原理,真实感兴趣的话,可以看上述提到的GO并发涉及的知识点,一点一点的深入下去,今天就到这里,大体了解GO协程的使用

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里,下一次GO的锁和原子操作分享

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

相关文章
|
1月前
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
2月前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
|
2月前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####
|
2月前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
2月前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
2月前
|
安全 Java Go
Go语言中的并发编程:掌握goroutine与通道的艺术####
本文深入探讨了Go语言中的核心特性——并发编程,通过实例解析goroutine和通道的高效使用技巧,旨在帮助开发者提升多线程程序的性能与可靠性。 ####
|
2月前
|
Go 开发者
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念,重点介绍了goroutines和channels的工作原理及其在实际开发中的应用。文章通过实例演示如何有效地利用这些工具来编写高效、可维护的并发程序,旨在帮助读者理解并掌握Go语言在处理并发任务时的强大能力。 ####
|
2月前
|
算法 安全 程序员
Go语言的并发编程:深入理解与实践####
本文旨在探讨Go语言在并发编程方面的独特优势及其实现机制,通过实例解析关键概念如goroutine和channel,帮助开发者更高效地利用Go进行高性能软件开发。不同于传统的摘要概述,本文将以一个简短的故事开头,引出并发编程的重要性,随后详细阐述Go语言如何简化复杂并发任务的处理,最后通过实际案例展示其强大功能。 --- ###
|
2月前
|
存储 安全 Go
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制。本文介绍了这两者的概念、用法及如何结合使用,实现任务的高效并发执行与数据的安全传递,强调了并发编程中的注意事项,旨在帮助开发者更好地掌握 Go 语言的并发编程技巧。
36 2
|
2月前
|
安全 Go 调度
Go语言中的并发编程:掌握goroutines和channels
在现代软件开发中,并发编程已经成为不可或缺的一部分。Go语言以其简洁的语法和强大的并发特性,成为了开发者的首选之一。本文将深入探讨Go语言中的两个核心概念——goroutines和channels,并通过实际代码示例展示如何使用它们来实现高效的并发处理。无论你是初学者还是有经验的开发者,通过本文的学习,你将能够更好地理解和应用Go语言的并发机制,提升你的编程技能。