程序
进程、线程、协程,不同级别的上下文切换
协程
协程,应用程序自己维护的上下文切换,非操作系统级别上下文,应用程序级别
协程,允许函数执行一半切换到另一个函数
- 独立栈空间
- 共享堆空间
- 用户级调度
对于单线程下,我们不可避免程序中出现IO操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到IO阻塞时就将寄存器上下文和栈保存到某个其他地方,然后切换到另外一个任务去计算。在任务切回来的时候,恢复先前保存的寄存器上下文和栈,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被CPU执行的状态。相当于我们在用户程序级别将自己的IO操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,IO比较少,从而会更多的将CPU的执行权限分配给我们。
线程是CPU控制的,而协程是程序自身控制的,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级。
简而言之,细化上下文是为了充分利用资源,以线程和协程为例,当线程A遇到IO,在没有协程时,会调度另一个线程B占用CPU,而不让A线程继续空占CPU浪费资源,而线程上下文切换是必要的资源损耗,如果引入协程,线程A遇到IO,实际上是函数C需要执行IO操作,则线程A调度自身的函数D继续计算,这样也能避免CPU空占,与线程调度相比,协程调度资源损耗更小,进程和线程也是类似关系
主死从随
graph TD
A[程序开始,主线程开始执行] --> B[go test开启协程]
B --> C[主线程代码继续执行]
C --> E[程序结束/主线程结束]
%% 将协程逻辑放在侧边
D[执行协程中代码逻辑..]
%% 连接主线程和协程逻辑
B -->|并发执行| D
协程基础
//协程
package main
import (
"fmt"
"runtime"
"time"
)
func test(name string) {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello " + name)
}
}
func main() {
fmt.Println(runtime.NumCPU())
go test("go1")
go test("go2")
go func() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello go3")
}
}() //必须要调用
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello main")
}
}
协程与闭包
package main
import(
"fmt"
"time"
)
func main() {
//注意,此处是一个经典的闭包,for+匿名函数,注意对比
for i := 0; i<10; i++ {
go func(){
fmt.Println("hello ", i)
}()//必须要调用
go func(n int){
fmt.Println("hello ", n)
}(i)//必须要调用
}
time.Sleep(time.Second * 3)
}
等待组
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup //值类型使用默认值
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("hello ", n)
}(i) //必须要调用
}
wg.Wait()
}
互斥锁
package main
import(
"fmt"
"sync"
)
var wg sync.WaitGroup
var total int
var lock sync.Mutex
func add() {
defer wg.Done()
for i := 0; i < 100000; i++ {
lock.Lock()
total += 1
lock.Unlock()
}
}
func sub() {
defer wg.Done()
for i := 0; i < 100000; i++ {
lock.Lock()
total -= 1
lock.Unlock()
}
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(total)
}
读写锁
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var total int
var lock sync.RWMutex
func read() {
lock.RLock()
wg.Add(1)
defer wg.Done()
for i := 0; i < 2; i++ {
time.Sleep(time.Second)
fmt.Println(total)
}
lock.RUnlock()
}
func write() {
wg.Add(1)
defer wg.Done()
for i := 0; i < 1000; i++ {
lock.Lock()
total += 1
lock.Unlock()
}
}
func main() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
go read()
}
go write()
wg.Wait()
fmt.Println(total)
}
原子操作
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
var i int64
var wg sync.WaitGroup
func add() {
atomic.AddInt64(&i, 1)
}
func addMulti() {
for j := 0; j < 10; j++ {
i++
time.Sleep(time.Millisecond * 1000)
i++
}
wg.Done()
}
func main() {
//CAS
wg.Add(3)
go addMulti()
go addMulti()
go addMulti()
wg.Wait()
fmt.Println(i)
}
sync.Once,保证只执行一次
sync.Map,协程安全的map
goroutine id
https://github.com/golang/go/issues/22770
package main
import (
"fmt"
"os"
)
func main() {
pid := os.Getpid()
fmt.Println("process id:", pid)
}
package main
import (
"fmt"
"runtime"
"strconv"
"strings"
"sync"
)
func GoId() int {
buf := make([]byte, 32)
n := runtime.Stack(buf, false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
return id
}
func main() {
fmt.Println("main", GoId())
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i, GoId())
}()
}
wg.Wait()
}