一、线程安全

现在有两个协程,同时触发,一个协程对一个全局变量进行100完成++操作,另一个对全局变量—的操作

那么,两个协程结束,最后的值应该是0才对

package main

import (
  "fmt"
  "sync"
)

var num int
var wait sync.WaitGroup

func add() {
  for i := 0; i < 100; i++ {
    num++
  }
  wait.Done()
}
func reduce() {
  for i := 0; i < 100; i++ {
    num--
  }
  wait.Done()
}

func main() {
  wait.Add(2)
  go add()
  go reduce()
  wait.Wait()
  fmt.Println(num)
}

但是你会发现,这个输出的结果完全无法预测。

原因是:CPU的调度方法为抢占式执行,随机调度

二、同步锁

采用锁的机制来解决以上问题

以下是使用 Go语言中的锁机制来解决上述问题的代码示例:

package main
import (
    "fmt"
    "sync"
)
var num int
var wait sync.WaitGroup
var lock sync.Mutex
func add() {
    // 加锁
    lock.Lock()
    for i := 0; i < 100; i++ {
        num++
    }
    // 解锁
    lock.Unlock()
    wait.Done()
}
func reduce() {
    // 加锁
    lock.Lock()
    for i := 0; i < 100; i++ {
        num--
    }
    // 解锁
    lock.Unlock()
    wait.Done()
}
func main() {
    wait.Add(2)
    go add()
    go reduce()
    wait.Wait()
    fmt.Println(num)
}

在这个示例中,我们使用了一个 sync.Mutex 类型的锁来保护对全局变量 num 的访问。在 addreduce 函数中,我们在对 num 进行操作之前先获取锁,操作完成后再释放锁。这样可以确保在任何时刻,只有一个协程能够访问和修改 num,从而保证了线程安全。

通过使用锁机制,我们可以得到正确的输出结果,即最后 num 的值为 0

三、线程安全下的map

当我们在一个协程函数下,读写map就会引发如下错误:

concurrent map read and map write

这个错误的意思就是map的线程安全错误

package main

import (
  "fmt"
  "sync"
  "time"
)

var wait sync.WaitGroup
var mp = map[string]string{}

func reader() {
  for {
    fmt.Println(mp["time"])
  }
  wait.Done()
}
func writer() {
  for {
    mp["time"] = time.Now().Format("15:04:05")
  }
  wait.Done()
}

func main() {
  wait.Add(2)
  go reader()
  go writer()
  wait.Wait()
}

我们不能在并发模式下读写map,如果想实现并发状态下读写map,需要添加如下操作

  1. 给读写操作加锁

  2. 使用sync.map

3.1 读写加锁

package main

import (
  "fmt"
  "sync"
  "time"
)

var wait sync.WaitGroup
var mp = map[string]string{}
var lock sync.Mutex

func reader() {
  for {
    lock.Lock()
    fmt.Println(mp["time"])
    lock.Unlock()
  }
  wait.Done()
}
func writer() {
  for {
    lock.Lock()
    mp["time"] = time.Now().Format("15:04:05")
    lock.Unlock()
  }
  wait.Done()
}

func main() {
  wait.Add(2)
  go reader()
  go writer()
  wait.Wait()
}

在原始的代码中,同时启动了一个读线程和一个写线程,它们并发地读写同一个 map。这会导致竞态条件,引发线程安全错误。

为了解决这个问题,添加了一个 sync.Mutex 锁。在读写 map 之前,通过调用 lock.Lock() 获取锁,读写完成后,通过调用 lock.Unlock() 释放锁。这样可以确保在同一时刻只有一个线程能够访问 map,避免了竞态条件。

通过这种方式,读线程和写线程可以安全地并发执行,而不会出现数据不一致或其他错误。

3.2 sync.map

package main

import (
  "fmt"
  "sync"
  "time"
)

var wait sync.WaitGroup
var mp = sync.Map{}

func reader() {
  for {
    fmt.Println(mp.Load("time"))
  }
  wait.Done()
}
func writer() {
  for {
    mp.Store("time", time.Now().Format("15:04:05"))
  }
  wait.Done()
}

func main() {
  wait.Add(2)
  go reader()
  go writer()
  wait.Wait()
}