一.并发性
1.并发产生的原因
在直观效果上,处理器是并行处理多项任务,因为我们可以在计算机上同时运行多个程序.但本质上一个处理器在某个时间点只能处理一个任务,属于串行执行。
在单处理器的情况下,并发问题源于多道程序设计系统的一个基本特性:进程的相对执行速度不可预测,它取决于其他进程的活动、操作系统处理中断的方式以及操作系统的调度策略。
在分布式环境下,并发产生的可能性就更大了,只要大家有依赖的共享资源,就有并发问题的出现,因为互相调用次序更加没法控制。
2.处理并发的方式
常见的处理并发的方式有:多进程,多线程,IO多路复用。
在实际的服务器应用中基于线程的并发模型并不能创建和使用过多的thread。因为thread数目过多于CPU核数,内核调度和切换thread将付出较大代价。因此通常在基于线程的基础上,在用户态设计用户态线程模型与调度器,使得调度不需要陷入内核完成,减小并发调度代价。
二.GO对并发性的支持
1.协程(coroutine)
1> 协程 (coroutine) 与 进程 (process), 线程 (thread)
在级别方面,进程(process)和线程(thread)处于操作系统(os)级别,他们是两个实际的“东西”(不说概念是因为这两个家伙的确不仅仅是概念,而是实际存在的,os的代码管理的资源),都是用来模拟“并行”的.写操作系统的程序员通过用一定的策略给不同的进程和线程分配CPU计算资源,来让用户“以为”几个不同的事情在“同时”进行“。在单CPU上,是os代码强制把一个进程或者线程挂起,换成另外一个来计算,所以,实际上是串行的。在现在的多核的cpu上,线程可能是“真正并行的”。与操作系统级别的进程和线程不同,协程 (coroutine) 是编译器级别的。Process和Thread看起来也在语言层次,但是内生原理却是操作系统先有这个东西,然后通过一定的API暴露给用户使用。
从调度方面,Process和Thread是os通过调度算法,保存当前的上下文,然后从上次暂停的地方再次开始计算,重新开始的地方不可预期,每次CPU计算的指令数量和代码跑过的CPU时间是相关的,跑到os分配的cpu时间到达后就会被os强制挂起。Coroutine是编译器的魔术,通过插入相关的代码使得代码段能够实现分段式的执行.对于Coroutine,是编译器帮助做了很多的事情,来让代码不是一次性的跑到底,而不是操作系统强制的挂起。代码每次跑多少,是可预期的。
2> 协程和子程序
Donald Knuth用一句话总结了协程的特点:“子程序就是协程的一种特例。”
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。
而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:
def A():
print '1'
print '2'
print '3'
def B():
print 'x'
print 'y'
print 'z'
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
1
2
x
y
3
z
3> 对协程的理解
- 轻量级"线程"(并不是正真的线程)
- 非抢占式的多任务处理,由协程主动交出控制权(线程是抢占式的多任务处理,切换控制权不在线程手中,在任何时候都有可能被操作系统进行切换,切换时需要将执行到一半的上下文存起来,使得切换需要消耗更多的资源,切换付出的代价比较大),正是由于非抢占式的多任务处理,只需在几个点进行切换,从而才使得协程更加轻量级
- 编译器 / 解释器 / 虚拟机层面上的多任务
- 多个协程可能在一个或多个线程上运行
2.go中的goroutine
goroutine其实是一种协程,在代码中通过关键字"go"来开启一个goroutine.
func main(){
for i:=0; i < 10; i++ { //重复开启1000个goroutine
go func(a int){ //开启一个不断输出 a 的goroutine
for{
fmt.Println(a)
}
}(i)
}
}
此时运行程序,会发现什么也没有输出,原因是main()函数也可以看做是是一个goroutine, 它与我们定义的goroutine是并发执行的,而main()函数运行太快了,我们定义的goroutine还没来得及运行就被退出了.解决方法是在main()中加一个延时.
func main(){
for i:=0; i < 10; i++ { //重复开启1000个goroutine
go func(a int){ //开启一个不断输出 a 的goroutine
for{
fmt.Println(a)
}
}(i)
}
time.Sleep(time.Millisecond)
}
对于非抢占式多任务处理的理解:
func main(){
var a [10]int
for i:=0; i < 10; i+