瞅一眼就能学会的GO并发编程使用教程

目录
  • GO的并发编程分享
  • 啥是并发编程呢
  • 为啥要有并发编程
  • 并发和并行的区别
  • 协程 goroutine 是啥
  • GO 高并发的原因是啥
  • GOLANG并发编程涉及哪些知识点呢
  • Goroutine的那些事
    • 如何使用 goroutine
    • 启动单个协程
    • 多个协程
  • GO 中的协程
    • goroutine 是如何调度
  • 总结

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并发编程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言并发编程基础上下文概念详解

    目录 前言 1 Go 中的 Context 2 Context 接口 3 Context Tree 4 创建上下文 4.1 上下文创建函数 4.2 Context 使用规范 4.3 Context 使用场景 5 总结 前言 相信大家以前在做阅读理解的时候,一定有从老师那里学一个技巧或者从参考答案看个:结合上下文.根据上下文我们能够找到有助于解题的相关信息,也能更加了解段落的思想. 在开发过程中,也有这个上下文(Context)的概念,而且上下文也必不可少,缺少上下文,就不能获取完整的程序信息.那

  • Go并发编程中sync/errGroup的使用

    目录 一.序 二.errGroup 2.1 函数签名 三.源码 3.1 Group 3.2 WaitContext 3.3 Go 3.4 Wait 四. 案例 五. 参考 一.序 这一篇算是并发编程的一个补充,起因是当前有个项目,大概の 需求是,根据kafka的分区(partition)数,创建同等数量的 消费者( goroutine)从不同的分区中消费者消费数据,但是总有某种原因导致,某一个分区消费者创建失败,但是其他分区消费者创建失败. 最初的逻辑是,忽略分区失败的逻辑,将成功创建的分区消费

  • Go并发编程之sync.Once使用实例详解

    目录 一.序 二. 源码分析 2.1结构体 2.2 接口 三. 使用场景案例 3.1 单例模式 3.2 加载配置文件示例 四.总结 五. 参考 一.序 单从库名大概就能猜出其作用.sync.Once使用起来很简单, 下面是一个简单的使用案例 package main import ( "fmt" "sync" ) func main() { var ( once sync.Once wg sync.WaitGroup ) for i := 0; i < 10;

  • Go并发编程中使用channel的方法

    目录 一.设计原理 二.数据结构 三.创建管道 四. 发送数据 4.1 直接发送 4.2 缓冲区 4.3 阻塞发送 4.4 小结 五. 接收数据 5.1 直接接收 5.2 缓冲区 5.3 阻塞接收 六. 关闭channel 七. 使用场景 7.1 使用channel控制子协程 7.2 通过关闭 channel 实现一对多的通知 7.3 使用 channel 做异步编程 7.4 超时控制 7.5 协程池 八. 参考 一.设计原理 Go 语言中最常见的.也是经常被人提及的设计模式就是: "不要通过共

  • golang并发编程的实现

    go main函数的执行本身就是一个协程,当使用go关键字的时候,就会创建一个新的协程 channel channel 管道,用于在多个协程之间传递信号 无缓存管道 当对无缓冲通道写的时候,会一直阻塞等到某个协程对这个缓冲通道读 阻塞场景: 通道中无数据,但执行读通道. 通道中无数据,向通道写数据,但无协程读取. 综上,无缓存通道的读写必须同时存在,且读写分别在两个不同的协程 func main(){ ch := make(chan int) go func(ch chan int){ ch <

  • 瞅一眼就能学会的GO并发编程使用教程

    目录 GO的并发编程分享 啥是并发编程呢 为啥要有并发编程 并发和并行的区别 协程 goroutine 是啥 GO 高并发的原因是啥 GOLANG并发编程涉及哪些知识点呢 Goroutine的那些事 如何使用 goroutine 启动单个协程 多个协程 GO 中的协程 goroutine 是如何调度 总结 GO的并发编程分享 之前我们分享了网络编程,今天我们来看看GO的并发编程分享,我们先来看看他是个啥 啥是并发编程呢 指在一台处理器上同时处理多个任务 此处说的同时,可不是同一个时间一起手拉手做

  • Python并发编程实例教程之线程的玩法

    目录 一.线程基础以及守护进程 二.线程锁(互斥锁) 三.线程锁(递归锁) 四.死锁 五.队列 六.相关面试题 七.判断数据是否安全 八.进程池 & 线程池 总结 一.线程基础以及守护进程 线程是CPU调度的最小单位 全局解释器锁 全局解释器锁GIL(global interpreter lock) 全局解释器锁的出现主要是为了完成垃圾回收机制的回收机制,对不同线程的引用计数的变化记录的更加精准. 全局解释器锁导致了同一个进程中的多个线程只能有一个线程真正被CPU执行. GIL锁每执行700条指

  • C#并发编程入门教程之概述

    写在前面 并发编程一直都存在,只不过过去的很长时间里,比较难以实现,随着互联网的发展,人口红利的释放,更加友好的支持并发编程已经成了主流编程语言的标配,而对于软件开发人员来说,没有玩过并发编程都会有点不好意思.本系列文章将会以C#语言为主,详细介绍并发编程. 什么是并发编程,其实很简单,并发编程就是在一台处理器上同时做多件事情,并发编程的目标就是充分利用处理器的每一个核,以达到最高的处理性能.举个例子,服务器在响应第一个请求的同时响应第二个请求. 关于并发编程的几个误解 误解一:并发编程就是多线

  • 详解Python并发编程之创建多线程的几种方法

    大家好,并发编程 今天开始进入第二篇. 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础.学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章. 本文目录 学会使用函数创建多线程 学会使用类创建多线程 多线程:必学函数讲解 经过总结,Python创建多线程主要有如下两种方法: 函数 类 接下来,我们就来揭开多线程的神秘面纱. . 学会使用函数创建多线程 在Python3中,Python提供了一个内置模块 threading.Thread,可以很方便地让我们创建多

  • Java并发编程之阻塞队列深入详解

    目录 1. 什么是阻塞队列 2. 阻塞队列的代码使用 3. 生产者消费者模型 (1)应用一:解耦合 (2)应用二:削峰填谷 (3)相关代码 4.阻塞队列和生产者消费者模型功能的实现 1. 什么是阻塞队列 阻塞队列是一种特殊的队列,和数据结构中普通的队列一样,也遵守先进先出的原则同时,阻塞队列是一种能保证线程安全的数据结构,并且具有以下两种特性:当队列满的时候,继续向队列中插入元素就会让队列阻塞,直到有其他线程从队列中取走元素:当队列为空的时候,继续出队列也会让队列阻塞,直到有其他线程往队列中插入

  • Java并发编程Semaphore计数信号量详解

    Semaphore 是一个计数信号量,它的本质是一个共享锁.信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可:当信号量中有可用的许可时,线程能获取该许可:否则线程必须等待,直到有可用的许可为止. 线程可以通过release()来释放它所持有的信号量许可(用完信号量之后必须释放,不然其他线程可能会无法获取信号量). 简单示例: package me.socketthread; import java.util.concurrent.ExecutorService;

  • Go并发编程实践

    前言 并发编程一直是Golang区别与其他语言的很大优势,也是实际工作场景中经常遇到的.近日笔者在组内分享了我们常见的并发场景,及代码示例,以期望大家能在遇到相同场景下,能快速的想到解决方案,或者是拿这些方案与自己实现的比较,取长补短.现整理出来与大家共享. 简单并发场景 很多时候,我们只想并发的做一件事情,比如测试某个接口的是否支持并发.那么我们就可以这么做: func RunScenario1() { count := 10 var wg sync.WaitGroup for i := 0;

  • Erlang并发编程介绍

    Erlang中的process--进程是轻量级的,并且进程间无共享.查了很多资料,似乎没人说清楚轻量级进程算是什么概念,继续查找中...闲话不提,进入并发编程的世界.本文算是学习笔记,也可以说是<Concurrent Programming in ERLANG>第五张的简略翻译. 1.进程的创建 进程是一种自包含的.分隔的计算单元,并与其他进程并发运行在系统中,在进程间并没有一个继承体系,当然,应用开发者可以设计这样一个继承体系.     进程的创建使用如下语法: 复制代码 代码如下: Pid

  • JAVA并发编程有界缓存的实现详解

    JAVA并发编程有界缓存的实现 1.有界缓存的基类 package cn.xf.cp.ch14; /** * *功能:有界缓存实现基类 *时间:下午2:20:00 *文件:BaseBoundedBuffer.java *@author Administrator * * @param <V> */ public class BaseBoundedBuffer<V> { private final V[] buf; private int tail; private int head

  • Java 并发编程:volatile的使用及其原理解析

    Java并发编程系列[未完]: •Java 并发编程:核心理论 •Java并发编程:Synchronized及其实现原理 •Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) •Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) •Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchroniz

随机推荐