并发编程开发将一个过程按照并行算法拆分为多个可以独立执行的代码块,从而充分利用多核和多处理器提高系统吞吐率
顺序、并发与并行
顺序是指发起执行的程序只能有一个
并发是指同时发起执行(同时处理)的程序可以有多个(单车道并排只能有一辆车,可同时驶入路段多辆车)
并行是指同时执行(同时做)的程序可以有多个 (多车道并排可以有多个车)
例程(Goroutine)
Go语言中每个并发执行的单元叫Goroutine,使用go关键字后接函数调用来创建一个Goroutine
main函数也是由一个例程来启动执行,这个例程称为主例程,其他例程叫工作例程。主例程结束后工作例程也会随之销毁,使用sync.WaitGroup(计数信号量)来维护执行例程执行状态
可以通过runtime包中的GoSched让例程主动让出CPU,也可以通过time.Sleep让例程休眠从而让出CPU
闭包陷阱
因为闭包使用函数外变量,当例程执行是,外部变量已经发生变化,导致打印内容不正确,可使用在创建例程时通过函数传递参数(值拷贝)方式避免
并发程序通信方式
共享数据(同步)
多个并发程序需要对同一个资源进行访问,则需要先申请资源的访问权限,同时再使用完成后释放资源的访问权。当资源被其他程序已申请访问权后,程序应该等待访问权被释放并被申请到时进行访问操作。同一时间资源只能被一个程序访问和操作
管道(异步)
数据处理者处理完数据后将数据放入缓冲区中,数据接收者从缓冲区中获取数据,处理者不用等待接收者是否准备好处理数据
共享数据
多个例程对同一个内存资源进行修改,未对资源进行同步限制,导致修改数据混乱
互斥锁
Go语言中sync包中提供了Mutex(互斥锁),可以用于对资源加锁和释放锁提供对资源同步方式访问
原子操作
原子操作是指过程不能中断的操作s,go语言sync/atomic包中提供提供了五类原子操作函数,其操作对象为整数型或整数指针
- Add*:增加/减少
- Load*:载入
- Store*:存储
- Swap*:更新
- CompareAndSwap*:比较第一个参数引用指是否与第二个参数值相同,若相同则将第一个参数值更新为第三个参数
管道
在go语言中可以通过chan来定义管道,可以通过操作符<-和->对管道进行读取和写入操作
通过管道维护例程状态
声明
管道是声明需要指定管道存放数据的类型,管道原则可以存放任何类型,但只建议用于存放值类型或者只包含值类型的结构体。在管道声明后,会被初始化为nil
初始化
使用make函数初始化,make(chan type)/make(chan type, len),不带len参数的用于创建无缓存区的管道,使用len创建指定缓冲区长度的管道
读取和写入
可通过操作符<-和->对管道进行读取和写入操作,当写入无缓冲区管道或由缓冲区管道已满时写入则会阻塞直到管道中元素被其他例程读取。同理,当管道中无元素时读取时也会阻塞到管道被其他例程写入元素
关闭管道
可通过close函数关闭管道,关闭后的管道不能被写入,当读取到最后一个元素后可通过读取的第二个参数用于判断是否结束
for-range遍历管道
管道也可以通过for-range进行遍历
只读和只写管道
可以在函数参数时声明管道为chan<-或chan->,表示管道只写或只读
select-case语句
当写入无缓冲区管道或由缓冲区管道已满时写入则会阻塞直到管道中元素被其他例程读取。同理,当管道中无元素时读取时也会阻塞到管道被其他例程写入元素,若需要同时对多个管道进行监听(写入或读取),则可以使用select-case语句
select语句自上到下执行case语句中对管道的读取和写入,当操作成功则执行对应子语句,否则执行下一个case语句,当所有case都失败,则执行default语句,default语句可省略
超时机制
可以通过select-case实现对执行操作超时的控制
Select-case语句监听每个case语句中管道的读取,当某个case语句中管道读取成功则执行对应子语句
Go语言time包实现了After函数,可以用于实现超时机制,After函数返回一个只读管道
sync包
sync包提供了同步原语,常用结构体有:
sync.Mutex:互斥锁
sync.RWMutex:读写锁
sync.Cond:条件等待
-
sync.Once:单次执行
sync.Map:例程安全映射
sync.Pool:对象池
sync.WaitGroup:组等待
runtime包
runtime包提供了与Go运行时系统交互的操作,常用函数:
- runtime.Gosched(): 当前goroutine让出时间片
- runtime.GOROOT(): 获取Go安装路径
- runtime.NumCPU(): 获取可使用的逻辑CPU数量
- runtime.GOMAXPROCS(1):设置当前进程可使用的逻辑CPU数量
- runtime.NumGoroutine(): 获取当前进程中goroutine的数量