《GO语言圣经》——基于共享变量的并发

69 阅读1分钟

互斥锁

goroutine间不仅可以使用channel来通信,同样的也可以使用锁保护共享资源来实现过个goroutine的通信。例如下面的程序由于没有锁的保护,最后输出的值并不为0:

package main

import (
    "fmt"
    "sync"
)

var count int = 0

func inc(wg *sync.WaitGroup) {
    for i := 0; i < 100000000; i++ {
       count++
    }
    wg.Done()
}

func dec(wg *sync.WaitGroup) {
    for i := 0; i < 100000000; i++ {
       count--
    }
    wg.Done()
}

func main() {
    wg := new(sync.WaitGroup)
    wg.Add(2)
    go inc(wg)
    go dec(wg)
    wg.Wait()
    fmt.Println("count:", count) //不为0
}

当给代码中对于临界资源的访问加上互斥锁之后,最后的结果就如我们所预期的为0

package main

import (
    "fmt"
    "sync"
)

var count int = 0
var mu sync.Mutex

func inc(wg *sync.WaitGroup) {
    for i := 0; i < 100000000; i++ {
       mu.Lock()
       count++
       mu.Unlock()
    }
    wg.Done()
}

func dec(wg *sync.WaitGroup) {
    for i := 0; i < 100000000; i++ {
       mu.Lock()
       count--
       mu.Unlock()
    }
    wg.Done()
}

func main() {
    wg := new(sync.WaitGroup)
    wg.Add(2)
    go inc(wg)
    go dec(wg)
    wg.Wait()
    fmt.Println("count:", count)  // 0
}

读写锁

读锁与写锁互斥,多个读锁之间可以共享,而写锁是独占的,具体不做阐述。

线程安全的变量初始化

在c++中,我们可以通过static关键字实现线程安全的局部变量初始化,进而实现单例模式。而go语言可以使用sync.Once实现线程安全的变量初始化。

package main

import (
    "fmt"
    "sync"
)

// Singleton 是一个我们将要确保线程安全初始化的类型
type Singleton struct{}

var (
    instance *Singleton
    once     sync.Once
)

// GetInstance 使用 sync.Once 确保 Singleton 实例的线程安全初始化
func GetInstance() *Singleton {
    once.Do(func() {
       instance = &Singleton{}
    })
    return instance
}

func main() {
    wg := sync.WaitGroup{}

    // 模拟多个goroutine同时请求实例
    for i := 0; i < 10; i++ {
       wg.Add(1)
       go func() {
          defer wg.Done()
          inst := GetInstance()
          fmt.Printf("获取的实例地址:%p\n", inst)
       }()
    }

    wg.Wait()
}