golang你不得不知道的事

浅析 go sync包

2021-03-17  本文已影响0人  超鸽带你飞

浅析 go sync包

背景介绍

尽管 Golang 推荐通过 channel 进行通信和同步,但在实际开发中 sync 包用得也非常的多

var a = 0
// 启动 100 个协程,需要足够大
// var lock sync.Mutex
for i := 0; i < 100; i++ {
    go func(idx int) {
        // lock.Lock()
        // defer lock.Unlock()
        a += 1
        fmt.Printf("goroutine %d, a=%d\n", idx, a)
    }(i)
}
// 等待 1s 结束主程序
// 确保所有协程执行完
time.Sleep(time.Second)

互斥锁sync.Mutex,读写锁sync.RWMutex

锁的一些概念及使用方法,

整个包围绕 Locker 进行,这是一个 interface:

type Locker interface {
        Lock()
        Unlock()
}

互斥锁 Mutex

func (m *Mutex) Lock()
func (m *Mutex) Unlock()

使用须知:

读写锁 RWMutex

func (rw *RWMutex) Lock()       //写锁定
func (rw *RWMutex) Unlock()     //写解锁

func (rw *RWMutex) RLock()      //读锁定
func (rw *RWMutex) RUnlock()    //读解锁

使用须知:

var count int
var rw sync.RWMutex

func main() {
    ch := make(chan struct{}, 10)
    for i := 0; i < 5; i++ {
        go read(i, ch)
    }
    for i := 0; i < 5; i++ {
        go write(i, ch)
    }

    for i := 0; i < 10; i++ {
        <-ch
    }
}

func read(n int, ch chan struct{}) {
    rw.RLock()
    fmt.Printf("goroutine %d 进入读操作...\n", n)
    v := count
    fmt.Printf("goroutine %d 读取结束,值为:%d\n", n, v)
    rw.RUnlock()
    ch <- struct{}{}
}

func write(n int, ch chan struct{}) {
    rw.Lock()
    fmt.Printf("goroutine %d 进入写操作...\n", n)
    v := rand.Intn(1000)
    count = v
    fmt.Printf("goroutine %d 写入结束,新值为:%d\n", n, v)
    rw.Unlock()
    ch <- struct{}{}
}

sync.Waitgroup,sync.Once

WaitGroup

用于等待一组 goroutine 结束,用法很简单。它有三个方法:

func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()

说明: Add 用来添加 goroutine 的个数。Done 执行一次数量减 1。Wait 用来等待结束.

注意: wg.Add() 方法一定要在 goroutine 开始前执行哦。

var wg sync.WaitGroup

for i, s := range seconds {
    // 计数加 1
    wg.Add(1)
    go func(i, s int) {
        // 计数减 1
        defer wg.Done()
        fmt.Printf("goroutine%d 结束\n", i)
    }(i, s)
}

// 等待执行结束
wg.Wait()
fmt.Println("所有 goroutine 执行结束")

Once

func (o *Once) Do(f func())

使用 sync.Once 对象可以使得函数多次调用只执行一次

var once sync.Once
onceBody := func() {
    fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
    go func() {
        once.Do(onceBody)
        done <- true
    }()
}
for i := 0; i < 10; i++ {
    <-done
}

----
# 打印结果
Only once

sync.Map

sync.Map是一个并发版本的Go语言的map

var m sync.Map
// m:=&sync.Map{}

// 添加元素
m.Store(1, "one")
m.Store(2, "two")

// 迭代所有元素
m.Range(func(key, value interface{}) bool {
    fmt.Printf("%d: %s\n", key.(int), value.(string))
    return true
})

// 获取元素1
value, ok := m.Load(1)
fmt.Println(value,ok)   //one true

// 返回已存value,否则把指定的键值存储到map中
value, loaded := m.LoadOrStore(1, "three")
fmt.Println(value,loaded)   //one true

value1, loaded1 := m.LoadOrStore(3, "three")
fmt.Println(value1,loaded1) //three false

m.Delete(3)

sync.Pool

在 golang 中有一个池pool,目的:
复用已经使用过的对象,来达到优化内存使用和回收的目的。
说白了,一开始这个池子会初始化一些对象供你使用,如果不够了呢,自己会通过new产生一些,当你放回去了之后这些对象会被别人进行复用,当对象特别大并且使用非常频繁的时候可以大大的减少对象的创建和回收的时间。

简单案例

一共只有三个方法我们需要知道的:New、Put、Get

var pool = sync.Pool{
    New: func() interface{} {
        return "123"
    },
}

func main() {
    t := pool.Get().(string)
    fmt.Println(t)

    pool.Put("321")
    
    t2 := pool.Get().(string)
    fmt.Println(t2)
}

---输出:
123
321

源码结构分析

type Pool struct {
    noCopy noCopy

    local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
    localSize uintptr        // size of the local array

    victim     unsafe.Pointer // local from previous cycle
    victimSize uintptr        // size of victims array

    // New optionally specifies a function to generate
    // a value when Get would otherwise return nil.
    // It may not be changed concurrently with calls to Get.
    New func() interface{}
}

// Local per-P Pool appendix.
type poolLocalInternal struct {
    private interface{} // Can be used only by the respective P.
    shared  poolChain   // Local P can pushHead/popHead; any P can popTail.
}

type poolLocal struct {
    poolLocalInternal

    // Prevents false sharing on widespread platforms with
    // 128 mod (cache line size) = 0 .
    pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

其实结构并不复杂,但是如果自己看的话有点懵。注意几个细节就ok。

1. Get的逻辑其实非常清晰:

image.png

2. Put逻辑就很简单了:

在看一个例子:

Put之后GC后Get

var pool = sync.Pool{
    New: func() interface{} {
        return "123"
    },
}

func main() {
    t := pool.Get().(string)
    fmt.Println(t)

    pool.Put("321")
    pool.Put("321")
    pool.Put("321")
    pool.Put("321")

    runtime.GC()
    time.Sleep(1 * time.Second)

    t2 := pool.Get().(string)
    fmt.Println(t2)

    runtime.GC()
    time.Sleep(1 * time.Second)

    t2 = pool.Get().(string)
    fmt.Println(t2)
}
---输出:
123
321
123

思考:

  1. 什么情况下适合使用sync.Pool呢?
  2. sync.Pool的对象什么时候会被回收呢?
  3. sync.Pool是如何实现线程安全的?

sync.Cond

上一篇下一篇

猜你喜欢

热点阅读