前言
Go是一种
静态强类型,``编译型
,并发型
并且具有垃圾回收
的编程语言
以上摘自维基百科Go语言的介绍.
从诞生之初,Go语言就备受瞩目,Google的支持,强大的开发团队,面向现代处理器的编程优化,一切都预示go语言的美好前景.
笔者的主力语言是Java,最近也抽空研究了一下Go语言,查到的资料中描述了很多Go的强大特性,这些特性都很诱人,但是还不足以称为语言的基础,本文将只阐述Go的并发特性
.
Go语言的高并发(concurrency)
concurrency(并发) is not parallelism(并行)
Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.
详见Concurrency is not parallelism
Go的并发基础: goroutine
A goroutine is a lightweight thread managed by the Go runtime.
goroutine是由Go runtime管理的比轻量级线程(LWP)更轻量的线程(姑且这么叫吧),它的开销很小,可以轻易的同时启动成千上万个goroutine,设想一下 Java中开启10个线程的消耗在Go语言中可以用来开启100甚至1000个goroutine,显然Go语言的运行效率会更高,要明白goroutine到底是什么,让我们先从线程开始讲起
从线程开始
线程的基础概念
线程分为三类kernel threads, user threads, and fibers
kernel threads 内核线程
kernel thread是由操作系统内核支持的线程,也是基本意义上的线程,kernel thread切换由内核完成.一般程序不会直接使用kernel thread,而是使用kernel thread的一种高级接口-轻量级进程(Light Weight Process)一个LWP对应于一个kernel thread
.
user threads 用户线程
user thread指不需要内核支持而在用户程序中实现的线程,它不依赖于内核,用户自己实现user thread的创建,同步,调度,管理等功能.这里一个LWP对应多个user thread,简单点说 一个进程拥有多个LWP,一个LWP又拥有多个user thread.
,这里的用户一般指的是编程语言
fiber
混合
两者的混合,折中方案,不多说
它们的优缺点都是什么
对于kernel thread和user thread,站在编程语言的角度
来看,都有什么优缺点
kernel thread
kernel thread的优点:
- kernel thread由内核管理,使用kernel thread时调用LWP的函数即可,对于编程语言来说实现简单
- 对于多核处理器,内核可以将线程调度到其他核心上运行,可以充分利用多核的优势
kernel thread的缺点:
- kernel thread的管理需要系统调用,而系统调用需要在用户态和内核态切换,代价很高
- 由于LWP和kernel thread是1:1的,每个LWP都要消耗一部分内核资源,这也限制了kernel thread的数量
user thread
user thread的优点:
- user thread的切换由编程语言控制,不涉及内核,也就很少需要内核态和用户态的切换,效率高
- 一个LWP对应多个user thread,因此user thread的数量要大大超过LWP.意味着更高的并发
user thread的缺点:
- 需要自己实现user thread的管理,很困难
- user thread是由用户管理的,而内核只能控制到LWP,那么在多核处理器下,user thread只能在某个核心上的某个线程上跑,无法充分利用多核优势
- 如果某个user thread由于某些原因被阻塞,那么这个LWP上的所有user thread都被阻塞了
综上所述,kernel thread由内核控制,使用简单但是效率不够高.user thread由用户(编程语言)实现,效率很高,但是实现起来比较复杂
.
在讲Go语言使用了哪种线程模式之前,先来看下Java
Java为什么从user thread转为kernel thread?
JDK1.2时使用的还是user thread,Java的开发团队称之为Green Thread
,为什么后面切换到了kernel thread呢?
查阅资料后在这里找到了原因,sco的说明
大致有一下两点原因
- Java开发团队在实现Green Threads时,为了达到非阻塞IO,对系统调用做了封装,但是某些JNI调用会打破这个封装,导致一些很严重的问题
- Green Threads无法充分利用多核优势,原因同user thread的缺点2
还有以下说明
here is a significant processing overhead for the JVM to keep track of thread states and swap between them, so green thread mode has been deprecated and removed from more recent Java implementations
总体来看是Java开发团队由于时代背景,user thread的实现有缺陷,因此才切换到kernel thread的线程模式
Go使用了哪个线程模型
Go使用了user thread的线程模型,在Go中称之为goroutine
,这是Go称为并发型语言的基础.
Go语言中有逻辑处理器(Logic Processor)的概念,,通过它完成user thread的调度.
下面看下goroutine如何解决user thread的缺陷
如何处理阻塞的user thread
下图截取自 <Go In Action>,可以看到当goroutine执行了阻塞的系统调用后,调度器将这个LWP与LP分离,并创建一个新的LWP为这个LP提供服务
如何充分利用多核
参见Fork/Join中的工作窃取算法,TODO
再谈内存模型
关于内存模型,参见共享内存和消息传递,总结一下
共享内存的
通信是隐式
的,线程之间通过写-读内存中的公共状态来隐式通信,而线程之间的同步是显式的
,必须在代码中写好同步逻辑.
消息传递的通信是显式
的,线程之间通过明确的发送消息来通信,由于收消息肯定在发消息之后,因此线程之间的同步是隐式的
目前的语言大都共享内存,为什么不用消息传递?以前的编程语言大都使用了kernel thread模式,而在kernel thread之间传递消息消耗太大,因此只能选择代码编写更麻烦的共享内存模型
Go使用了哪种内存模型
消息传递!
,Go使用了user thread的线程模式.消息传递的消耗不再成为问题,自然要选择更有利于并发的消息传递模型,在Go中,消息传递主要通过chan
这个结构完成chan即通道.类似于Java nio中通道的概念.goroutine通过在chan中发送/读取消息实现同步.
结语
本文简要说明了Go语言高并发的实现基础,可以看到Go的高并发是语言设计时天生的优势.
网友评论