互斥锁
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()
}