-
并发和并行
-
golang里的并发指的是让某个函数独立于其他函数执行的能力(goroutine运行时是相互独立的)。
-
并行是让多个不同的代码片段同时在不同的物理处理器上执行。
-
并行是同时做很多事情,并发是同时管理很多事情。
- goroutine
- waitgroup
- waitgroup.add(int) //int为需要运行的goroutine数量
- waitgroup.done() //标识一个goroutine运行结束
- waitgroup.wait() //等待所有goroutine运行结束
- 多个goroutine同时并发运行时,如果当前执行的goroutine占用逻辑处理器时间过长,调度器会暂停当前执行的goroutine并切换到其他goroutine执行,其他goroutine执行结束或执行其他goroutine一定时间之后,调度器会切换回暂停的goroutine继续执行。
- waitgroup
- Runtime.GOMAXPROCS(1)/runtime.GOMAXPROCS(runtime.NumCPU)
- GOMAXPROCS()函数可以为可用的物理处理器(CPU)创建逻辑处理器
- Runtime.Gosched()
- 当前goroutine从线程退出,并放回到队列
- 竞争状态
- 如果两个或多个goroutine在没有相互同步的情况下,访问某个共享资源,并试图同时读和写这个资源,就处于相互竞争的状态。
- 对一个共享资源的读和写操作必须是原子化的。
- 资源锁
- 传统同步goroutine机制,就是对共享资源加锁。
- 原子函数
- 原子函数能以很底层的加锁机制来同步访问整型变量和指针。
- atomic.AddInt64(&num,n)为int64类型整型数值安全的增加n(串行增加)
- atomic.LoadInt64(&num) 取出int64类型整型的值
- atomic.StoreInt64(&num,n)为int64类型整型数值赋值为n
- 互斥锁
- 互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine能够访问临界区的代码
- sync.Mutex----->mutex.Lock(){代码块}----->mutex.Unlock()
- 当一个goroutine进入临界区,并在临界区代码块执行过程中强制退出当前线程(runtime.Gosched()),调度器会字词分配这个goroutine进入线程继续运行,直到该goroutine执行Unlock()释放代码块,其他goroutine才能进入该临界区
- 通道
- 除了原子函数和互斥锁外,通道也能保证对共享资源的安全访问以及消除竞争状态,通道通过发送和接收需要共享的资源,在goroutine之间做到资源同步。
- 声明通道时,需要指定将要被共享的数据的类型
- 可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针
- 通道需要通过内置函数make()来创建通道。
- chan := make(chan int)----------无缓冲通道
- buffer := make(chan string,10)---------有缓冲通道
- 创建通道需要使make函数的第一个参数是关键字“chan”,之后跟着允许通道交换的数据类型,如果创建的是有缓冲通道,还需要第二个参数指定该通道的缓冲区大小
- 通过通道发送值或指针需要用到“<-”操作符
-
buffer <- "abcd"
-
-
goroutine从通道获取数据
-
value := <-buffer
-
- 无缓冲通道
- 无缓冲通道是指在接收前没有能力保存任何值的通道,这种通道需要发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。
- 如果发送goroutine和接收goroutine没有同时准备好,会造成发送或接收操作的goroutine阻塞等待。
- 有缓冲通道
- 有缓冲通道是一种在被接收前能存储一个或者多个值的通道。不强制要求goroutine之间必须同时完成发送和接受。
- 通道中没有要接收的值时,接收动作会阻塞。
- 通道没有可用的缓冲区容纳被发送的值时,发送动作会阻塞。
- 无缓冲通道能保证发送和接受会在同一时间进行数据交换,有缓冲通道没有这种保证。
- 总结:
- 并发是指goroutine之间相互独立的运行
- goroutine在逻辑处理器上执行,逻辑处理器具有独立的系统线程和运行队列
- 竞争状态是指两个或多个goroutine试图同时访问同一个资源
- 原子函数和互斥锁可以防止竞争状态
-
-
并发模式
- 程序可以在分配的时间内完成工作,正常终止
- 程序没有及时完成工作,“自杀”
- 接受到操作系统发送的中断事件,程序立刻尝试清理状态并终止工作。
- 使用通道来监视程序的执行时间,如果程序运行时间太长,可以终止程序
- 程序没有及时完成工作,“自杀”
- 使用通道监视操作系统中断事件(os.signal),打断程序运行
- pool包(sync/pool.go)
- 用于使用有缓冲通道实现资源池,管理可以在任意数量的goroutine之间共享及独立使用的资源。(共享一组静态资源的情况下,如共享数据库连接或者内存缓冲区)。
- 实现io.Closer接口的资源就可以用pool资源池来管理。
- 向pool包提供共享资源的工厂函数和资源数量,由pool包来管理资源池
- work包
- 用于使用无缓冲的通道来创建一个goroutine池,这些goroutine执行并控制一组工作,让其并发执行。
- 因为执行相同的工作,goroutine既不需要工作队列,也不需要相互配合执行
- 当goroutine池中所有的goroutine都忙,无法接收新的工作时,能及时通过通道通知调用者
- 使用无缓冲通道不会有工作在队列里丢失或者卡住,所有工作都会被处理。
- 总结:
- 可以使用通道来控制程序的声明周期(正常终止,中断,超时(“自杀”)等)
- select的default语句可以用来尝试向通道发送或接收数据,而不会阻塞
- 有缓冲的通道适合用来管理可复用的资源
- 有缓冲通道适合用来处理并发任务
- 无缓冲通道可以保证两个goroutine之间的数据交换,且保证发送方对方接收到了数据