一、并发基础
-
并发的应用场景:
-
图形用户界面
-
Web服务
-
"事务处在分布式环境上,相同的工作单元在不同的计算机上处理着被分片的数据",不理解!!!
-
CPU从单内核向内核发展
-
阻塞的IO操作
-
-
并发的优点:
-
更客观的表现问题模型
-
充分利用CPU的多内核
-
充分利用CPU与其他硬件设备固有的异步性
-
-
并发的实现模型:
-
多进程:
-
操作系统层面的并发;
-
开销大
-
-
多线程:
-
操作系统层面的并发;
-
开销较多进程小,但依然较大
-
-
基于回调的非阻塞/异步IO:
-
在Node.js中有较好的实践
-
编程比多线程复杂
-
-
协程:
-
用户态层面的并发;
-
关于用户态层面的并发的理解可参考:goroutine背后的系统知识:http://www.sizeofvoid.net/goroutine-under-the-hood/ 再读三遍!!!
-
-
可以在语言层面支持,或者通过库支持
-
开销极小,编程简单,结构清晰;
-
-
二、协程
-
“可轻松创建上百万个协程”协程也需要上下文环境的保存,其上下文环境比系统线程要小很多吗???
-
语言层面支持协程的优点:
-
Go语言标准库提供的所有系统调用操作(包括同步IO操作),都会让出CPU给其他goroutine。
-
上述“系统调用操作”存在于哪些包中???
-
-
库层面支持协程的缺点:
-
与语言层面的优点相对应:一个协程调用同步IO操作,其他协程也会阻塞,无法达到期望的目标。
-
三、goroutine
-
在函数调用前加上go关键字,即启动了一个goroutine;
-
函数返回,goroutine运行结束;
-
函数返回值被忽略;
四、并发通信
-
两种并发通信模型:共享数据和消息;
-
Go使用消息机制进行通信;在goroutine间尽量不共享变量;
-
关于channel参考:goroutine和channel的简单理解:http://www.cnblogs.com/yjf512/archive/2012/06/06/2537712.html
五、channel
-
channel是进程内的多个goroutine间的通信方式;跨进程通信使用Socket或HTTP协议。
-
channel是类型相关的,一个channel只能传递一种类型的值。
-
基本语法:
-
声明channel: var chanName chan ElementType,示例如下:
-
var ch chan int // 声明一个int型的channel;注意此时ch未初始化,值为nil;
-
var chm map[string] chan bool // 声明一个map,其值为bool型的channel;
-
charr := make([]chan int, 10)
-
声明并初始化了一个包含10个int型channel的数组;
-
注意此时数组元素并未初始化,其值为nil;
-
每个元素初始化时,仍然需要使用make()函数,示例:charr[0] = make(chan int)
-
-
-
定义一个channel:使用make函数,示例如下:
-
ch = make(chan int)
-
-
写入与读取chan:
-
ch := make(chan int)
-
写入:
-
语法示例:ch <- 1
-
结论:直到有goroutine读取该chan后,否则该写入操作一直阻塞;
-
-
读取:
-
语法示例:value := <- ch
-
结论:读取操作一直阻塞,直到有goroutine向该chan写入数据;
-
-
注意:以上两点结论,仅针对不带缓冲的channel;
-
-
-
select:
-
Go在语言级别支持select关键字,用于处理异步IO问题。
-
用法示例:
-
select {
-
case <- chan1:
-
// 如果chan1成功读到数据,则进行该case处理语句;
-
-
case chan2 <- 1:
-
// 如果成功向chan2写入数据,则进行该case处理语句;
-
-
default:
-
// 如果上面都没有成功,则进入default处理流程;
-
-
-
}
-
注意:每个case语句里必须有一个IO操作。
-
-
-
缓冲机制:
-
创建带缓冲的channel:ch := make(chan int, 10) // make的第二个参数为channel的缓冲区大小。
-
缓冲区被填满前,写入操作是不会阻塞的。
-
如果单词写入操作比缓冲区大,会阻塞吗???
-
-
超时机制:
-
go语言没有提供直接的超时处理机制。
-
value := <- ch,如果一直没有想ch写入数据,会导致该goroutine一直阻塞。
-
-
利用select关键字,实现超时处理。示例如下:
-
timeout := make(chan bool, 1)
-
go func() {
-
time.Sleep(time.Second)
-
timeout <- true
-
-
} () // 定义匿名函数,并在goroutine中运行;
-
select {
-
case <- ch:
-
// other process
-
-
case <- timeout:
-
// 在1秒中内ch中一直没有可读数据时,timeout可读,select执行结束,从而不再去一直读取ch。
-
-
-
}
-
-
-
channel的传递:
-
channel同int等基本类型一样,也可以通过另一个channel传递给其他goroutine;
-
-
单向channel:
-
语法示例:
-
声明:
-
var ch1 chan int // 双向channel
-
var ch2 chan<- int // 单向只写channel
-
var ch3 <-chan int // 单向只读channel
-
-
初始化:
-
ch4 := make(chan int) // 双向channel
-
ch5 := chan<- int(ch4) // 单向只写channel
-
ch6 := <-chan int(ch4) // 单向只读channel
-
-
-
类型转换:
-
如上述ch4、ch5和ch6可以将channel在单向和双向之间进行类型转换
-
-
用法示例:
-
func Parse(ch <-chan int) {
-
// 一般单向channel用于函数参数中,
-
// 用来提示该函数实现对参数channel只能进行指定方向的操作。
-
-
}
-
-
-
关闭channel:
-
语法示例:close(ch)
-
判断channel是否已关闭:
-
value, ok := <-ch,如果ok为false,则ch已关闭。
-
-
六、多核并行化
-
Go1.0(或Go1.1)版本中,多个goroutine在多核CPU上并发运行时,实际上只在一个核心上运行。
-
多个goroutine并发执行时,运行时间较单一执行流是否明显缩短,使用Benchmark测试???
-
怎么确定有多少个CPU核心在工作???
-
解决方法:
-
设置环境变量GOMAXPROCS的值为CPU核心数;
-
或者在启动goroutine前调用如下方法:runtime.GOMAXPROCS( NumCPU() )
-
-
确认在Go的哪个版本开始,Go能够智能的利用多核资源???
-
七、让出时间片
-
方法:在goroutine中调用runtime.Gosched()函数,可以是该goroutine主动让出时间片。
-
课下需要详细了解runtime包的具体功能。
八、同步
-
同步锁:
-
互斥锁:sync.Mutex
-
读写锁:sync.RWMutex
-
注意:
-
为避免死锁,可以在获得锁后,在defer关键字中释放锁,然后再对共享资源进行操作。
-
defer在函数运行结束后才会执行,最好将对共享资源的操作封装在独立的函数中。
-
-
-
全局唯一性操作:
-
sync.Once类型:
-
保证了全局唯一操作(如:全局初始化)在多个goroutine中,仅被执行一次;
-
并且在该全局唯一操作执行结束前,其他goroutine处于阻塞状态;
-
-
sync.atomic子包中,提供了一些基础数据类型的原子操作函数,需要多了解。
-
九、完整示例