进程和线程
- 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的 一个独立单位。
- 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更 小的能独立运行的基本单位。
- 一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行。
并发和并行
- 多线程程序在一个核的cpu上运行,就是并发
- 多线程程序在多个核的cpu上运行,就是并行
协程和线程
- 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于 用户级线程,这些用户级线程的调度也是自己实现的。
- 线程:一个线程上可以跑多个协程,协程是轻量级的线程。
可以说,协程与线程主要区别是它将不再被内核调度,而是交给了程序自己而线程是将自己交给内核调度,所以也不难理解golang中调度器的存在。
示例:
package main import "fmt"
import "time" func test() {
var i int
for {
fmt.Println(i)
time.Sleep(time.Second)
i++
}
} func main() {
go test()
for {
fmt.Println("i' running in main")
time.Sleep(time.Second)
}
}
goroutine调度模型
golang的goroutine是如何实现的? 知乎上一篇介绍文章。
- M: 代表真正的内核OS线程,和POSIX里的thread差不多,真正干活的人。
- G: 代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
- P: 代表调度的上下文,可以把它看做一个局部的调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。
图中看,有2个物理线程M,每一个M都拥有一个context(P),每一个也都有一个正在运行的goroutine。
P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。
图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue)。
P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。
图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue)。
图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。
三者关系的宏观的图为:
如何设置golang运行的cpu核数
package main import (
"fmt"
"runtime"
) func main() {
num := runtime.NumCPU()
runtime.GOMAXPROCS(num)
fmt.Println(num)
}
备注:go1.8版本之后可以不用设置,默认使用所有CPU核数。
锁示例:
package main import (
"fmt"
"sync"
"time"
) var (
m = make(map[int]uint64)
lock sync.Mutex
) type task struct {
n int
} func calc(t *task) {
var sum uint64
sum = 1
for i := 1; i < t.n; i++ {
sum *= uint64(i)
} fmt.Println(t.n, sum)
lock.Lock()
m[t.n] = sum
lock.Unlock()
} func main() {
for i := 0; i < 16; i++ {
t := &task{n: i}
go calc(t)
} time.Sleep(10 * time.Second)
lock.Lock()
for k, v := range m {
fmt.Printf("%d! = %v\n", k, v)
}
lock.Unlock()
}
goroutine中使用recover
应用场景,如果某个goroutine panic了,而且这个goroutine里面没有 捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产 生一个goroutine,就需要写下recover。
package main import (
"fmt"
"runtime"
"time"
) func test() { defer func() {
if err := recover(); err != nil {
fmt.Println("panic:", err)
}
}() var m map[string]int
m["stu"] = 100
} func calc() {
for {
fmt.Println("i'm calc")
time.Sleep(time.Second)
}
} func main() {
num := runtime.NumCPU()
runtime.GOMAXPROCS(num - 1)
go test()
for i := 0; i < 2; i++ {
go calc()
} time.Sleep(time.Second * 10000)
}
Go并发原理 https://i6448038.github.io/2017/12/04/golang-concurrency-principle/
Golang非CSP并发模型外的其他并行方法总结 https://i6448038.github.io/2018/12/18/Golang-no-csp/