penglongli / blog

18 stars 1 forks source link

Go 的 mutex 和 atomic #116

Open penglongli opened 6 years ago

penglongli commented 6 years ago

Go 的 Mutex 和 atomic

在 Go 中可以利用加锁的方式来保证并发安全,如下代码:

var (
    sum = 0
    wg sync.WaitGroup
)

func incr() {
    defer wg.Done()

    for i := 0; i < 100000;i++  {
        sum++
    }
}

func main() {
    wg.Add(2)

    go incr()
    go incr()

    wg.Wait()
    fmt.Println(sum)
}

我们的目标是输出 sum = 200000,但是结果并不理想。因为 sum 是全局变量,会被两个 goroutine 操作,在操作的过程中可能会出现一个 goroutine 覆盖另一个 goroutine 值的情况出现。

我们下边可以利用两种方法来解决:

sync.mutex 加锁

var (
    sum = 0
    mutex sync.Mutex
    wg sync.WaitGroup
)

func incr() {
    defer wg.Done()

    for i := 0; i < 100000;i++  {
        mutex.Lock()
        {
            sum++
        }
        mutex.Unlock()
    }
}

func main() {
    wg.Add(2)

    go incr()
    go incr()

    wg.Wait()
    fmt.Println(sum)
}

我们在对 sum 进行操作的时候加锁,防止出现并发安全问题

atomic

var (
    sum int32
    wg sync.WaitGroup
)

func incr() {
    defer wg.Done()

    for i := 0; i < 100000;i++  {
        atomic.AddInt32(&sum, 1)
    }
}

func main() {
    wg.Add(2)

    go incr()
    go incr()

    wg.Wait()
    fmt.Println(sum)
}

我们使用 atomic 包对 sum 进行安全的并发操作

mutex in struct

如下代码:

type User struct {
    sync.Mutex
    Name string
    Age int
}

func userSleep(user *User) {
    defer w.Done()

    user.Lock()
    fmt.Println(user.Name + " sleeping...")
    time.Sleep(5 * time.Second)
    fmt.Println(user.Name + " wake up.")
    user.Unlock()
}

var (
    w sync.WaitGroup
)

func main() {
    u1 := User{
        Name: "Tom",
        Age: 23,
    }

    w.Add(2)
    go userSleep(&u1)
    go userSleep(&u1)
    w.Wait()
}

输出:

Tom sleeping...
// Sleep 5s
Tom wake up.
Tom sleeping...
// Sleep 5s
Tom wake up.

可以看到两个 goroutine 是串行执行的,这是因为一个 goroutine 获得了锁开始做事情,另外一个阻塞等待获得锁

嵌套结构体

type User struct {
    sync.Mutex
    Name string
    Age int
}

type Male struct {
    User
    Val string
}

func maleSleep(male *Male) {
    defer w.Done()

    male.Lock()
    fmt.Println(male.Name + " sleeping...")
    time.Sleep(5 * time.Second)
    fmt.Println(male.Name + " wake up.")
    male.Unlock()
}

var (
    w sync.WaitGroup
)

func main() {
    u := User{
        Name: "Tom",
        Age: 23,
    }
    m := Male{
        User: u,
        Val: "test",
    }

    w.Add(2)
    go maleSleep(&m)
    go maleSleep(&m)
    w.Wait()
}

在上述我们声明了一个嵌套结构体 Male, 使用如上所示。

关于嵌套结构体可以用下述方法初始化: