并发主要由切换时间片来实现"同时"运行,并行则是直接利用多核实现多线程的运行,go可以设置使用核数,以发挥多核计算机的能力。

线程A、B、C在单核上运行

线程A、B、C在多核上运行
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。
是由官方实现的超级"线程池",每个实例4~5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因。
goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
在Go语言中,每一个并发的执行单元叫作一个goroutine
goroutine是由Go的运行时(runtime)调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。
Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。
在Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能——goroutine,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。
在调用函数(普通函数和匿名函数都行)时在前面加上go关键字,就可以为一个函数创建一个goroutine。
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数
func hello() {
fmt.Println("Hello Goroutine!")
}
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
}
// 输出
main goroutine done!
为什么没有打印
Hello Goroutine!
在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。
当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束
var wg sync.WaitGroup
func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}
//输出
Hello Goroutine! 5
Hello Goroutine! 7
Hello Goroutine! 3
Hello Goroutine! 0
Hello Goroutine! 1
Hello Goroutine! 2
Hello Goroutine! 6
Hello Goroutine! 8
Hello Goroutine! 4
上面使用了sync.WaitGroup来实现goroutine的同步,多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。
OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB。
GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统
channels则是goroutine之间的通信机制,可以让一个goroutine通过它给另一个goroutine发送值信息。
每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int
使用make:
ch := make(chan int) // 没有缓存类型为int的channelch = make(chanint, 3) // 缓存为3的channelchanel是一种引用类型,当复制或进行参数传递时,只是拷贝了一个channel引用
channel零值是nil
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相同的对象,那么比较的结果为真。一个channel也可以和nil进行比较。
发送接收数据都使用<-运算符
ch <- x // 发送
x = <- ch // 接收
<-ch // 不使用接收结果的接收
若channel中没有数据了,就会产生一个零值的数据。
使用内置函数close()进行channel的关闭:close(ch)
对于已关闭的channel,任何发送操作会导致panic,而接收操作仍可以接收到之前的数据。
发送操作将导致发送者goroutine进入阻塞状态,直到同一个channel中的数据被另一个goroutine接收。
反之,若接收操作先发生,那么接收者goroutine也会进入阻塞状态,直到有发送者发送数据。
基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作
因此可以通过无缓存channels来使main goroutine等待另一个goroutine执行完毕
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) // NOTE: ignoring errors
log.Println("done")
done <- struct{}{} // signal the main goroutine
}()
mustCopy(conn, os.Stdin)
conn.Close()
<- done // wait for background goroutine to finish
}
基于channels发送消息有两个重要方面。首先每个消息都有一个值,但是有时候通讯的事实和发生的时刻也同样重要。当我们更希望强调通讯发生的时刻时,我们将它称为消息事件。有些消息事件并不携带额外的信息,它仅仅是用作两个goroutine之间的同步,这时候我们可以用struct{}空结构体作为channels元素的类型,也可以使用bool或int类型实现同样的功能
带缓冲区的channels与生产者消费者模式相似
sync.Mutex
未完待续。。。