通常程序以独立方式编写,简单易维护,但一些时候处于性能考量就需要并发,go语言的并发是基于csp模型的,通信顺序进程(Communicating Sequential Processes,CSP),不像其他语言对数据加锁,而是通过使用通道channel在gorountine中传递信息
并发与并行
并发的概念是离不开程序进程与线程的概念的,通常进程指一个运行的程序,其中包含了线程,启动时的线程是主线程,操作系统会调度线程,1.5前每个可用的物理处理器分配一个逻辑处理器,1.5后整个应用程序则只分配一个逻辑处理器,来调度所有的goroutine
如图,调度器将goroutine排列到队列里,绑定线程,将goroutine依次执行,如果系统调用阻塞的话,就新开线程等待返回,同时继续处理下一个routine,而针对网络io的话,阻塞时routine会与逻辑处理器分离,放到网络轮询器的运行时上,当网络轮询器指示操作就绪时,再回到逻辑处理器执行
逻辑处理器数量没有上限,但线程数默认10000,超过就会崩溃,可以通过调用runtime/debug包的SetMaxThreads方法来更改
并发不同于并行,它不是同时执行多个,而是调配管理多个,在合适的时机处理合适的任务,在有限的硬件资源下,具有更好的表现
如果要实现并行,就要有多个逻辑处理器,调度器会平均分配routine到多个线程,并且要有多个物理处理器,这样才能达到并行的效果
如图是并发与并行的区别
goroutine
// 这个示例程序展示如何创建goroutine
// 以及调度器的行为
package main
import (
"fmt"
"runtime"
"sync"
)
// main是所有Go程序的入口
func main() {
// 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)
// wg用来等待程序完成
// 计数加2,表示要等待两个goroutine
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Start Goroutines")
// 声明一个匿名函数,并创建一个goroutine
go func() {
// 在函数退出时调用Done来通知main函数工作已经完成
defer wg.Done()
// 显示字母表3次
for count := 0; count < 3; count++ {
for char := 'a'; char < 'a'+26; char++ {
fmt.Printf("%c ", char)
}
}
}()
// 声明一个匿名函数,并创建一个goroutine
go func() {
// 在函数退出时调用Done来通知main函数工作已经完成
defer wg.Done()
// 显示字母表3次
for count := 0; count < 3; count++ {
for char := 'A'; char < 'A'+26; char++ {
fmt.Printf("%c ", char)
}
}
}()
// 等待goroutine结束
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating Program")
}
这里两个匿名函数分别输出26个小写和大写字母,使用wait等待返回后,逻辑调度器停止,运行结果是
这里由于一个goroutine执行的太快,还没切换到第二个goroutine就已经全部输出完了
其中waitgroup是指两个运行的goroutine,done是通知执行完毕,这样做是防止一个routine执行太久,可以手动停止
如下是一个长时间运行的go示例,可以观察这一现象
// 这个示例程序展示goroutine调度器是如何在单个线程上
// 切分时间片的
package main
import (
"fmt"
"runtime"
"sync"
)
// wg用来等待程序完成
var wg sync.WaitGroup
// main是所有Go程序的入口
func main() {
// 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)
// 计数加2,表示要等待两个goroutine
wg.Add(2)
// 创建两个goroutine
fmt.Println("Create Goroutines")
go printPrime("A")
go printPrime("B")
// 等待goroutine结束
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("Terminating Program")
}
// printPrime 显示5000以内的素数值
func printPrime(prefix string) {
// 在函数退出时调用Done来通知main函数工作已经完成
defer wg.Done()
next:
for outer := 2; outer < 5000; outer++ {
for inner := 2; inner < outer; inner++ {
if outer%inner == 0 {
continue next
}
}
fmt.Printf("%s:%d\n", prefix, outer)
}
fmt.Println("Completed", prefix)
}