青训营笔记

82 阅读4分钟

Go中的并发编程

这是我参与【第五届青训营】伴学笔记创作活动的第16天

1.Goroutine:

使用goroutine

  • Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。
  • 一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。

  • 1.G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
  • 2.P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • 3.M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;

2.runtime包:

runtime.Gosched():

  • 让出CPU时间片,重新等待安排任务
func main() {
    go func(s string) {
        for i := 0; i < 2; i++ {
            fmt.Println(s)
        }
    }("world")
    // 主协程
    for i := 0; i < 2; i++ {
        // 切一下,再次分配任务
        runtime.Gosched()
        fmt.Println("hello")
    }
}

runtime.Goexit():

  • 退出当前协程
func main() {
    go func() {
        defer fmt.Println("A.defer")
        func() {
            defer fmt.Println("B.defer")
            // 结束协程
            runtime.Goexit()
            defer fmt.Println("C.defer")
            fmt.Println("B")
        }()
        fmt.Println("A")
    }()
    for {
    }
}

runtime.GOMAXPROCS

  • Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。

3.Channel:

3.1 channel是一种类型,一种引用类型。声明通道类型的格式如下:

    var 变量 chan 元素类型
    //例如:
	var ch1 chan int   // 声明一个传递整型的通道
  • 通道是引用类型,通道类型的空值是nil。

3.2 声明的通道后需要使用make函数初始化之后才能使用。

make(chan 元素类型, [缓冲大小])
ch4 := make(chan int)

3.3 channel操作:

  • 具有发送(send),接收(receive),关闭(close)三种操作
ch <- 10 //把10发送到ch
x := <- ch //从ch中接收并赋值给x
close(ch) //关闭通道

3.4 有缓冲的通道

我们可以在使用make函数初始化通道的时候为其指定通道的容量,例如:

func main() {
    ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
    ch <- 10
    fmt.Println("发送成功")
}

4.定时器:

func main() {
    // 1.timer基本使用
    timer1 := time.NewTimer(2 * time.Second)
    t1 := time.Now()
    fmt.Printf("t1:%v\n", t1)
    t2 := <-timer1.C
    fmt.Printf("t2:%v\n", t2)

    // 2.验证timer只能响应1次
    timer2 := time.NewTimer(time.Second)
    for {
     <-timer2.C
     fmt.Println("时间到")
    }

    // 3.timer实现延时的功能
    //(1)
    time.Sleep(time.Second)
    //(2)
    timer3 := time.NewTimer(2 * time.Second)
    <-timer3.C
    fmt.Println("2秒到")
    //(3)
    <-time.After(2*time.Second)
    fmt.Println("2秒到")

    // 4.停止定时器
    timer4 := time.NewTimer(2 * time.Second)
    go func() {
     <-timer4.C
     fmt.Println("定时器执行了")
    }()
    b := timer4.Stop()
    if b {
     fmt.Println("timer4已经关闭")
    }

    // 5.重置定时器
    timer5 := time.NewTimer(3 * time.Second)
    timer5.Reset(1 * time.Second)
    fmt.Println(time.Now())
    fmt.Println(<-timer5.C)

    for {
    }
}

5.并发安全和锁:

5.1 互斥锁:

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
    for i := 0; i < 5000; i++ {
        lock.Lock() // 加锁
        x = x + 1
        lock.Unlock() // 解锁
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
}

5.2 读写互斥锁

  • 读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
var (
    x      int64
    wg     sync.WaitGroup
    lock   sync.Mutex
    rwlock sync.RWMutex
)

func write() {
    // lock.Lock()   // 加互斥锁
    rwlock.Lock() // 加写锁
    x = x + 1
    time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
    rwlock.Unlock()                   // 解写锁
    // lock.Unlock()                     // 解互斥锁
    wg.Done()
}

func read() {
    // lock.Lock()                  // 加互斥锁
    rwlock.RLock()               // 加读锁
    time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
    rwlock.RUnlock()             // 解读锁
    // lock.Unlock()                // 解互斥锁
    wg.Done()
}

func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }

    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
}

6.Sync:

6.1 sync.WaitGroup:

  • Go语言中可以使用sync.WaitGroup来实现并发任务的同步
  • (wg *WaitGroup) Add(delta int) : 计数器+delta
  • (wg *WaitGroup) Done() : 计数器-1
  • (wg *WaitGroup) Wait() : 阻塞直到计数器变为0
var wg sync.WaitGroup

func hello() {
    defer wg.Done()
    fmt.Println("Hello Goroutine!")
}
func main() {
    wg.Add(1)
    go hello() // 启动另外一个goroutine去执行hello函数
    fmt.Println("main goroutine done!")
    wg.Wait()
}

6.2 sync.Once

var icons map[string]image.Image

var loadIconsOnce sync.Once

func loadIcons() {
    icons = map[string]image.Image{
        "left":  loadIcon("left.png"),
        "up":    loadIcon("up.png"),
        "right": loadIcon("right.png"),
        "down":  loadIcon("down.png"),
    }
}

// Icon 是并发安全的
func Icon(name string) image.Image {
    loadIconsOnce.Do(loadIcons)
    return icons[name]
}

6.3 sync.Map:

  • Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。
var m = sync.Map{}

func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func(n int) {
            key := strconv.Itoa(n)
            m.Store(key, n)
            value, _ := m.Load(key)
            fmt.Printf("k=:%v,v:=%v\n", key, value)
            wg.Done()
        }(i)
    }
    wg.Wait()
}