Go 并发
- 并发指的是同时处理多个任务的能力。
- 并行指的是并行处理多个任务的能力。
并行不一定加快运行速度,因为并行组件之间可能需要互相通信。
Go中使用协程,信道来处理并发。
协程
Go中主要通过协程实现并发。
协程是与其他函数或方法一起并发运行的函数或方法,协程可以看作是轻量级线程,但是创建成本更小,我们经常会看见数以千计的协程并发运行。
协程相比线程的优势
- Go协程创建成本低,堆栈大小只要几K,并可以根据应用需要进行递减,线程必须指定堆栈大小,堆栈是固定不变的。
- Go中多个协程会复用数量更少的OS线程,就是可能数千个协程公用一个线程。如果线程中一个协程阻塞,OS会再创建一个线程,并把其他的协程复制到新的线程上。
- Go通过Channel来进行协程间通信,Channel防止多个协程访问共享内存时发生竞态条件,信道是Go协程间通信的管道。
创建协程
调用函数或者方法时,在前面加上关键字go
可以运行在新协程上。
func hello(){
}
func main(){
go hello()
}
Main函数运行在主协程上,hello并发的运行在新协程上。
我们启动一个新协程,这个新协程会立即返回,而不会等到函数或方法执行完毕。 如果主协程终止,其他协程也会终止。
我们可以在主协程中使用休眠阻塞主协程,等待协程执行完毕。
信道(Channel)
信道是协程间通信的管道。
每个信道都关联一个类型,信道只能运输这种类型的数据,传输其他类型数据是违法的。
chan T 表示T传输类型的信道
可以使用make来定义信道。
a := make(chan int)
通过信道进行发送和接收数据
data : = < - a //读取信道
a <- data // 写入信道
通过信道旁边的箭头指定是发送数据还是接收数据。
信道的发送和接收默认是阻塞的
当把数据发送到信道时,发送数据语句发生阻塞直到其他Go协程从信道读取到数据才会解除阻塞。
同样,当读取信道数据时,如果没有其他协程把数据写入信道,读取过程会一直阻塞。
Go协程和信道特性没有像其他语言需要加锁和同步的开销。
func hello(done chan bool){
done < - true // 向信道中写入数据
}
func main(){
done := make(chan bool)
go hello(done)
< - done // 接收数据,会发生阻塞
}
接收多个信道值:
s, c := <-sq, <-cu
死锁
信道使用过程中需要考虑的重点是死锁,当协程给一个信道发送数据时,按理说其他协程会来接收数据,如果没有的话,会形成死锁。 同样等的从信道读取数据是,也会产生死锁。
单向信道
上面的都是双向信道,即通过信道既能发送数据也能接受数据,我们也可以创建单向信道,只能发送或者接受数据。
sendch := make(chan<-int) // 创建了只能发送数据的单项信道
当我们尝试从此信道读取数据时,会报错。
Go中可以把双向信道转换成单向信道这样单向信道能正常读写,但是不能把单向信道转成双向信道。
关闭信道和遍历信道
数据发送方可以关闭信道,通知接收方这个信道不再产生新数据。
检查信道是否关闭:
v, ok := <- ch // 接收数据,同时用另一个变量检查关闭状态
如果ok为false,说明通道关闭,得到的值为零值。
func producer(chnl chan int){
for I := 0; I < 10; I++{
chill <- I // 循环0~9写入信道
}
close(chnl) // 关闭信道
}
func main(){
ch := make(chan int)
go producer(ch)
for{ // 主函数死循环
v, ok := <-ch
if ok == false{ // 检查到信道关闭
break
}
}
}
缓冲信道
上面的都是无缓冲信道,接收和发送数据过程都是阻塞的。
缓冲信道,只有在缓冲满的情况下才会阻塞缓冲信道发送。只有在缓冲为空的时候,才会阻塞信道获取。
通过make函数传递一个容量参数,代表缓冲大小,可以创建缓冲信道。
ch := make(chan type, capacity)
capacity容量应该大于0,无缓冲信道默认容量为0。
同样当缓冲信道堵塞到最大容量时也会产生死锁。
信道长度和容量
容量代表信道可以存储的值数量,使用make函数创建缓冲信道时候会指定容量大小。
缓冲信道长度指的是信道中当前队列元素个数。