10.并发编程

予早 2024-12-21 09:51:13
Categories: Tags:

程序

进程、线程、协程,不同级别的上下文切换

协程

协程,应用程序自己维护的上下文切换,非操作系统级别上下文,应用程序级别

协程,允许函数执行一半切换到另一个函数

对于单线程下,我们不可避免程序中出现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()
}