前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >入门参考:从Go中的协程理解串行和并行

入门参考:从Go中的协程理解串行和并行

原创
作者头像
soolaugust
修改2020-11-27 18:04:39
1.5K0
修改2020-11-27 18:04:39
举报
文章被收录于专栏:雨夜随笔

Go语言的设计亮点之一就是原生实现了协程,并优化了协程的使用方式。使得用Go来处理高并发问题变得更加简单。今天我们来看一下Go中的协程。

从串行到并行

在处理器还是单个单核的时候,这个时候并不存在并行,因为只有一个处理器。所以那时候的编程都是串行编程。程序执行都是从头顺序执行到尾。到了多处理器多核的时代,为了充分利用处理器的处理能力,开始出现了并发编程。开发者开始在进程中启用多个线程来执行操作,利用CPU的调度能力来最大化程序处理效率。

并发,并行

在说到并发编程的时候总会遇到这两个概念,面试的时候也会问道,在这里就简单说一下这两者的区别:

并发是一种能力,是指多个任务在一段时间内同时发生。 并行值得是多个任务同时发生,就是并行。

并发值得是并行的能力,并发不一定是同时发生,可能是同一时间段内交替发生。

进程,线程,协程

进程和线程是操作系统的基本概念:

进程:指计算机中已运行的程序,进程是程序的基本执行实体。 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程的实际运行单位。

那么协程是在线程之上,更加轻量级的设计。协程因为只工作在用户控件,没有线程上下文切换带来的消耗。协程的调度由用户手动切换,所以更加灵活。

协程的另一大优势就是因为在用户空间调度,所以不会出现代码执行一半被强制中断,所以无需原子操作锁。

Go中的协程

在Go中使用协程非常简单,就使用go关键字就可以了。我们来看一段串行代码使用协程如何进行操作:

代码语言:javascript
复制
package main

import (
    "fmt"
    "time"
)

func main(){
    print1To10()
}

func print1To10(){
    for i := 1; i<=10; i++{
        fmt.Printf("%d ", i)
    }
}

// 输出
// 1 2 3 4 5 6 7 8 9 10 

那么使用协程,我们来看一下运行结果:

代码语言:javascript
复制
package main

import (
    "fmt"
    "time"
)

func main(){
    fmt.Println("before go coroutine")
    go print1To10()
    fmt.Println("after go coroutine")
    time.Sleep(100 * time.Millisecond) //防止主协程直接结束了,打印协程还没来得及执行
}

func print1To10(){
    for i := 1; i<=10; i++{
        fmt.Printf("%d ", i)
    }
}

// 输出
/***********
before go coroutine
after go coroutine
1 2 3 4 5 6 7 8 9 10 
*************/

我们可以看出使用go关键词后,打印并不是按照顺序串行执行的,而是在主协程执行结束后,打印协程才开始执行。

Go协程的调度机制

Go中的协程调度模型是G-P-M模型:

G代表Goroutine,也就是Go中的协程对象。 P代表Processor,代表虚拟的处理器。一般来说,和逻辑核一一对应。 M代表Machine,实际上是操作系统的线程。

image.png
image.png

这里我们简单说一下Go的调度机制,感兴趣或者有了解的可以自行看Go的源码:

  1. 在Go程序启动时,会给每个逻辑核分配一个P(虚拟处理器)
  2. 同时,Go会创建一个主协程G,来执行程序。新创建的G会被放到LRQ(P上的本地G队列)或者GRQ(全局G队列)。
  3. 给P分配一个M(内核线程),这些M由OS Scheduler调度而非Go Scheduler调度。M用来运行G
  4. P会尽可能获取G来运行,当没有G运行后,会销毁并重新进入调度

其中第4条 尽可能获取G 则是Go的有趣的设计理念之一,当一个 P 发现自己的 LRQ 已经没有 G 时,会从其他 P “偷” 一些 G 来运行。看看这是什么精神!自己的工作做完了,为了全局的利益,主动为别人分担。这被称为 Work-stealing

再看串行和并行

这里我们以Go协程来继续说一下串行和并行,对于习惯于串行编程的程序员来说,理解并行可能稍微需要点时间,对于程序设计来说,并行的设计主要是为了提高程序运行的效率,使得程序能够充分利用多核多处理器的资源(或者多机器)。那么对于如何充分利用,大部分支持并行编程的语言都有其内部的调度机制,即使没有,也会使用系统的调度机制--线程调度。

那么对于并行调度机制总体上分为两类:协作式和抢占式

协作式:一个任务得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU ,所以任务之间需要协作使用一段时间的 CPU ,放弃使用,其它的任务也如此,才能保证系统的正常运行。如果有一个任务死锁,则系统也同样死锁。 抢占式:总控制权在操作系统手中,操作系统会轮流询问每一个任务是否需要使用 CPU ,需要使用的话就让它用,不过在一定时间后,操作系统会剥夺当前任务的 CPU 使用权,把它排在询问队列的最后,再去询问下一个任务。如果有一个任务死锁,系统仍能正常运行。

在 Go1.1 版本中,调度器还不支持抢占式调度,只能依靠 goroutine 主动让出 CPU 资源,存在非常严重的调度问题。

Go1.12 中编译器在特定时机插入函数,通过函数调用作为入口触发抢占,实现了协作式的抢占式调度。但是这种需要函数调用主动配合的调度方式存在一些边缘情况。

后面Go在1.14版本实现了基于信号的真抢占式调度。用于解决解决了垃圾回收和栈扫描时存在的问题。

Go的协程调度目前虽然不能称得上完美,但是对于我们理解并行有一定的帮助。所谓并行编程,就是开启多个任务而不用等待任务结果。可以使得相互独立的任务同时运行,比如文件写入等。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从串行到并行
  • 并发,并行
  • 进程,线程,协程
  • Go中的协程
  • Go协程的调度机制
  • 再看串行和并行
相关产品与服务
物联网
腾讯连连是腾讯云物联网全新商业品牌,它涵盖一站式物联网平台 IoT Explorer,连连官方微信小程序和配套的小程序 SDK、插件和开源 App,并整合腾讯云内优势产品能力,如大数据、音视频、AI等。同时,它打通腾讯系 C 端内容资源,如QQ音乐、微信支付、微保、微众银行、医疗健康等生态应用入口。提供覆盖“云-管-边-端”的物联网基础设施,面向“消费物联”和 “产业物联”两大赛道提供全方位的物联网产品和解决方案,助力企业高效实现数字化转型。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档