文章目录
- 看一个线程不安全的例子:
- 互斥锁
- 读写锁
- WaitGroup监控goroutine退出状态
- sync.Cond条件变量实现生产者,消费者
- 死锁(deadlock)产生的原因
- sync.Once保证某段代码只执行一次
- 单例模式的实现
阅读本文前需要对并发机制有了解
只要是多线程,操作全局变量,就会有线程安全的问题,跟语言无关,同样,go也有互斥锁,读写锁这些。注意:go里面精确的名字是goroutine,这里我就叫做线程
看一个线程不安全的例子:
package main
import (
"fmt"
"sync"
"time"
)
var n = 0
func main() {
for j := 0;j<10;j++ {
go func() {
for i := 0;i<10000;i++ {
n++
}
}()
}
for {
time.Sleep(time.Millisecond * 200)
fmt.Println(n)
}
}
结果肯定不是100000
要想解决这个问题:就是加锁
互斥锁
写go代码的人是幸福的,代码就是这么简洁。
package main
import (
"fmt"
"sync"
"time"
)
var mux sync.Mutex
var n = 0
func main() {
for j := 0;j<10;j++ {
go func() {
for i := 0;i<10000;i++ {
mux.Lock()
n++
mux.Unlock()
}
}()
}
for {
time.Sleep(time.Millisecond * 200)
fmt.Println(n)
}
}
读写锁
如果读的场景远大于写的场景,用读写锁性能更好,更易发挥多核CPU优势,如下20个goroutine读,2个写,用读写锁更好
package main
import (
"fmt"
"sync"
"time"
)
var mux sync.RWMutex
var n = 0
func main() {
for j := 0;j<2;j++ {
go func() {
for i := 0;i<10000;i++ {
mux.Lock()
n++
mux.Unlock()
}
}()
}
for j := 0;j<20;j++ {
go func() {
for i := 0;i<10000;i++ {
time.Sleep(time.Millisecond * 200)
mux.RLock()
fmt.Println(n)
mux.RUnlock()
}
}()
}
for {
time.Sleep(time.Hour)
}
}
WaitGroup监控goroutine退出状态
WaitGroup解决的是并发线程的退出问题,通俗点讲,也就是主线程要等所有线程都退出后才能退出。go的实现同样非常简单
package main
import (
"fmt"
"sync"
)
// 开一个group,go程有多少就Add多少,当go程退出的时候,就Done把数量减1,用wait监控数量,空了就退出
var wg sync.WaitGroup
func main() {
for i:=0;i<1000;i++ {
go func(i int) {
wg.Add(1)
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
}
sync.Cond条件变量实现生产者,消费者
这里注意,生产者发出信号后,会有至少一个消费者q.cond.Wait()
获得信号,所以对边界条件需要再次判断
借助sync.Cond可以实现chan
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
type Queue struct {
queue []string
cond *sync.Cond
}
// producer
func (q *Queue) Enqueue(str string) {
q.cond.L.Lock()
q.queue = append(q.queue, str)
q.cond.L.Unlock()
q.cond.Signal()
}
// consumer
func (q *Queue) Dequeue() string {
q.cond.L.Lock()
if len(q.queue) == 0 {
q.cond.Wait()
}
// 为防止多个协程接收到条件成立信号,必须判断
if len(q.queue) == 0 {
q.cond.L.Unlock()
return ""
}
str := q.queue[0]
q.queue = q.queue[1:]
q.cond.L.Unlock()
return str
}
func main() {
q := Queue{
cond: sync.NewCond(&sync.Mutex{}),
}
go func() {
for i := 0; i < 10000; i++ {
time.Sleep(time.Millisecond * 100)
q.Enqueue(strconv.Itoa(i))
}
}()
for {
time.Sleep(time.Millisecond * 100)
go func() {
fmt.Println(q.Dequeue())
}()
go func() {
fmt.Println(q.Dequeue())
}()
}
time.Sleep(time.Hour)
}
死锁(deadlock)产生的原因
死锁的意思就是有一个锁,永远都解不开,阻塞在拿锁的位置,需要注意,死锁不一定报错,只有在主go程中出现死锁才报错,程序挂掉。
死锁产生的原因就2个:
- 加锁了,但是没释放,例子:
package main
import (
"fmt"
"sync"
"time"
)
var n int
var mux sync.Mutex
func main() {
for i := 0; i < 2; i++ {
go func() {
mux.Lock()
n++
if i > 10 {
mux.Unlock()
}
fmt.Println(n)
}()
}
time.Sleep(time.Hour)
}
- 嵌套锁:
package main
import (
"fmt"
"sync"
"time"
)
var n int
var mux sync.Mutex
func main() {
go func() {
mux.Lock()
mux.Lock()
n++
mux.Unlock()
mux.Unlock()
}()
time.Sleep(time.Hour)
}
sync.Once保证某段代码只执行一次
单例模式的实现
package main
import (
"fmt"
"sync"
"time"
)
var once sync.Once
var cg *Config
type Config struct {
Port int
cg *Config
}
func NewConfig() *Config {
once.Do(func() {
cg = &Config{Port: 8888}
})
return cg
}
func main() {
c1 := NewConfig()
c2 := NewConfig()
c1.Port = 1000
fmt.Println(c2.Port)
time.Sleep(time.Hour)
}