Go concurrent

并发是指在同一时间内可以执行多个任务,Golang通过编译器运行时,从语言级别上支持并发特性,因此Golang在语言层面天生支持并发。

Golang的并发通过Goroutine特性来完成的,Goroutine类似于线程,属于用户态的线程。可根据需要创建多个Goroutine并发工作。与传统的线程不同的是,Goroutine是由Golang运行时调度完成的,而线程则是由操作系统调度完成的。

另外,Golang还提供了Channel在多个Goroutine之间进行通信。Goroutine和Channel是Golang秉承CSP(Communicating Sequential Process)并发模式的重要实现基础。

基础概念

进程(Process)

  • 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的独立单位。
  • 一个进程可以创建和撤销多个线程,同一进程中多个线程之间可以并发执行。
  • 进程拥有自己独立的堆和栈,即不共享堆也不共享栈,进程由操作系统调度。

线程(Thread)

  • 线程是进程的一个执行实体,是CPU调度和分配的基本单位。
  • 线程是比进程更小且能独立运行的基本单位
  • 线程拥有自己独立的栈和共享的堆,即共享堆不共享栈,线程由操作系统调度。

协程(Coroutine)

  • 一个线程上可以跑多个协程,协程是轻量级的线程。
  • 协程具有独立的栈空间同时共享堆内存,协程的调度由开发人员自己控制,本质上类似于用户态线程。
  • 协程和线程一样共享堆但不共享栈,协程是由开发人员在编码中进行手工调度。

并发

多线程程序在单核CPU上运行时称为并发,多线程程序在多核CPU上运行则称为并行。并发与并行不同,并发是由切换时间片来实现同步执行,并行则是直接利用多核实现多线程的运行。

  • 并发(concurrency):并发是把任务在不同的时间点交给处理器进行处理,在同一时间点任务并不会同时运行。
  • 并行(parallelism):并行是将每个任务分配给每个处理器独立来完成,在同一个时间点,任务一定是同时运行的。

并发不是并行,并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键词是同时做很多事情,而并发是指同时管理很多事情,这些事情制作了一般就被暂停区做别的。

很多情况下,并发的效果比并行好,因为操作系统和硬件的总资源一般很少,但能支持系统同时做很多事情。这种使用较少的资源做更多事情的哲学也是直到Go语言设计的哲学。

并发编程的难点在于协调,因为协调需要通过交流。因此并发单元之间的通信是最大的问题。在工程上,通常有两种并发通信模型分别是共享数据和消息。共享数据是指多个并发单元分别保持对同一个数据的引用,以实现对该数据的共享。被共享的数据可能具有多种形式,比如内存数据块、磁盘文件、网络数据等。实际工程应用中,最为常见的则是内存,也就是所谓的共享内存。

Go语言针对并发编程提供的通信模型是以消息机制而非共享内存作为通信方式。消息机制认为每个并发单元是自包含且独立的个体,它们拥有自己的变量。在不同并发单元之间这些变量不会共享。每个并发单元的输入和输出只有一种就是消息。这点类似于进程的概念,因为每个进程不会被其它进程打扰,它们只做好自己的工作就可以了。不同进程之间靠消息来通信,它们自身不会共享内存。Go语言提供的消息通信机制被称为channel通道。

Goroutine

Java或C++中实现并发编程时通常需要开发人员自己维护一个线程池,需要自己去包装一个又一个的任务,同时还需自己去调度线程执行任务并维护上下文切换,这一切工作消耗着开发人员大量地心智。能不能有一种机制,开发人员仅需定义多个任务,操作系统把定义的任务分配到CPU上来实现并发执行呢?

Golang中Goroutine就是这样一种机制,Go程序会智能地将Goroutine中的任务合理地分配给每个CPU。

Goroutine实现并行则必须使用多个逻辑处理器,调度器会将Goroutine平等地分配到每个逻辑处理器上,这会让Goroutine在不同的线程上运行。不过想要真的实现并行的效果,用户需要让自己的程序运行在具有多个物理处理器的机器上。否则哪怕Go语言运行时使用多个线程,Goroutine依然会在同一物理处理器上并发运行,达不到并行的效果。

goroutine

有并发就会存在资源竞争,若多个Goroutine在没有相互同步的情况下访问某个共享的资源,比如同时对某个资源进行读写时,就会处于相互竞争的状态,也就是并发中的资源竞争。并发本身并不复杂,但由于存在资源竞争的问题,使得开发出好的并发程序会变得复杂,因为会引起很多莫名其妙的问题。

Thread

Goroutine是建立在Thread之上的轻量级的抽象,允许以非常低的代价在同一个地址空间中并行第执行多个函数或方法。相比于Thread,Goroutine的创建和销毁的代价要小很多,而且它的调度是独立于线程的。

Goroutine是用户态的线程,Thread是操作系统级(内核态)的线程。

Goroutine并不会比Thread运行得更快,Goroutine只会增加更多的并发性。因为当一个Goroutine被阻塞(比如等待I/O)时,Go的Scheduler会调度其它可以执行的Goroutine继续运行。

相比于Thread而言Goroutine优点在于

  • 内存消耗更少:Goroutine所需的内存通常只有20KB,而Thread则需要1MB,之间500倍差距。

  • 创建于销毁的开销更小

Thread创建时需要向操作系统申请资源,销毁时必须将资源归还,因此创建和销毁时的开销更大。而Goroutine的创建和销毁是由Go语言在运行时自己管理的,因此更低。

  • 切换开销更小

Goroutine实现高并发的主要原因由于切换开销更小,这也是与Thread最主要的区别。

Thread的调度方式是抢占式的,如果一个Thread的执行时间超过了分配给它的时间片,就会被其它可执行的Thread抢占。

在Thread切换的过程中需要保存和恢复所有及寄存器信息,比如16个通用寄存器、PC(Program Counter)程序计数器、SP(Stack Pointer)堆栈指针、段寄存器等等。

Goroutine的调度是协同式的,不会直接与操作系统内核打交道。当Goroutine进行切换时,只有少量的寄存器需要保存和恢复,因此切换效率更高。

Goroutine是Go并行设计的核心。Go语言内部实现了Goroutine之间的内存共享。

Goroutine具有可增长的栈,Thread具有固定的栈内存。

操作系统的Thread一般都具有固定的栈内存,通常为2MB。一个Goroutine的栈在其生命周期开始时只需要很小的栈,典型情况下是2KB。

Goroutine的栈不是固定的,可以按需增加或缩小,可根据相应的数据进行伸缩。Goroutine的栈大小限制可达到1GB(极少会使用这么大),因此Golang中一次性可创建十万左右的Goroutine同时运行并发任务。

Coroutine

Python、Lua、C#语言都会支持Coroutine的特性,Coroutine和Goroutine类似都可以将函数或语句在独立的环境中运行,不同之处在于Goroutine可能发生并行执行,但Coroutine始终是顺序执行的。

Goroutine和Coroutine的概念和运行机制均脱胎于早期的操作系统,Coroutine的运行机制属于协作式的任务处理,早期操作系统要求每个应用必须遵守操作系统的任务处理规则,应用程序在不需要使用CPU时会主动交出CPU使用权。若开发者无意间或故意让应用程序长时间占用CPU,操作系统也无能为力,表现出来就是计算机会很容易失去响应或死机。

Goroutine属于抢占式任务处理,和现有的多线程和多进程处理类似。应用程序对CPU的控制权最终还需由操作系统来管理,操作系统如果发现某个应用长时间大量占用CPU,此时用户有权终止这个任务。

Goroutine意味着并行通过Channel来通信的,Coroutine使用yieldresume操作。Coroutine是通过让出和恢复操作来通信,因此Goroutine比Coroutine更强大。

狭义地讲,Goroutine可能发生在多线程环境下,Goroutine无法控制自己获取高优先级支持。而Coroutine始终发生在单线程中,Coroutine程序需要主动交出控制权,宿主才能获得控制权并将控制权交给其它的Coroutine。

简单地将Goroutine归纳为Coroutine并不合适,Goroutine会在运行时创建多个线程来执行并发任务,Goroutine可被调度到其它线程中并行执行,因此更像是多线程和协程的综合体,能最大限度地提升执行效率,以发挥多核处理能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值