go的GMP并发模型,让go天然支持高并发,先了解一下GMP模型吧
GMP
G协程,M工作线程、P处理器,M必须持有P才可以执行G
P维护着一个协程G队列,P依次将G调度到M中运行
if M0中G0发生系统调用,M0将释放P,冗余的M1获取P,继续执行P队列中剩余的G。(只要P不空闲就充分利用了CPU)
G0系统调用结束后,如果有空闲的P,则获取P继续执行G0,否则将G0放入全局队列,M0进入缓存池睡眠。(全局队列中的G主要来自从系统调用中恢复的G)
下面介绍一下编程常用的同步(synchronize)原语
互斥锁 mutex
rwmutex
,要了解自旋锁和
Once
多用于实现单例模式
饿汉模式,一般是直接创建一个包级变量直接使用即可,注意既然是单例模式,就不能让他人随意创建,类型要是私有的,使用接口暴露方法,让外部获得私有变量
懒汉模式,在第一次使用时创建,这里需要注意并发安全,可以使用sync.Once
来保证并发安全
type Singleton interface {
Work() string
}
type singleton2 struct{}
func (s *singleton2) Work() string {
return "singleton2 is working"
}
func newSingleton2() *singleton2 {
return &singleton2{}
}
var (
instance *singleton2
once sync.Once
)
// GetSingleton2 用于获取单例模式对象
func GetSingleton2() Singleton {
once.Do(func() {
instance = newSingleton2()
})
return instance
}
Pool
sync.Pool 是 Go 语言标准库中的一个并发安全的对象池,用于缓存和重用临时对象,以提高性能。通常用于减少内存分配和垃圾回收的开销。PS:一般将变量Put回Pool中,需要将变量重置
var studentPool = sync.Pool{
New: func() any {
return new(Student)
},
}
func BenchmarkUnmarshalWithPool(b *testing.B) {
for n := 0; n < b.N; n++ {
stu := studentPool.Get().(*Student)
json.Unmarshal(buf, stu)
studentPool.Put(stu)
}
}
Cond
Cond是条件变量,可以让一组goroutine等待某个条件的发生。当条件发生时,调用Broadcast或者Signal来通知所有等待的goroutine继续执行。
需要注意的是 sync.Cond 都要在构造的时候绑定一个 sync.Mutex。Wait() 和 Signal() 函数必须在锁保护下的临界区中执行。Wait()一般放在for循环中,因为可能会出现虚假唤醒
var mu = &sync.Mutex{}
var done = false
func read(name string, c *sync.Cond) {
c.L.Lock()
for !done {
c.Wait()
}
log.Println(name, "reading")
c.L.Unlock()
}
func write(name string, c *sync.Cond) {
time.Sleep(1 * time.Second)
c.L.Lock()
done = true
c.L.Unlock()
log.Println(name, "writting finish")
c.Broadcast()
}
func main() {
cond := sync.NewCond(mu)
go read("reader1", cond)
go read("reader2", cond)
go read("reader3", cond)
write("writer", cond)
time.Sleep(time.Second * 3)
}
Channel
WaitGroup
注意Wait后不能再Add,否则会panic
扩展:errgroup,可以捕获goroutine中的panic,只要有一个goroutine出错,就会取消所有的goroutine
atomic
原子操作,用于解决并发问题,比如计数器,锁等
func main() {
// 定义一个共享的计数器,使用 int64 类型
var counter int64
// 使用 WaitGroup 来等待所有 goroutine 完成
var wg sync.WaitGroup
// 启动多个 goroutine 来增加计数器的值
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
// 在每个 goroutine 中原子地增加计数器的值
for j := 0; j < 100000; j++ {
atomic.AddInt64(&counter, 1)
}
wg.Done()
}()
}
// 等待所有 goroutine 完成
wg.Wait()
// 输出最终的计数器值
fmt.Println("Final Counter Value:", atomic.LoadInt64(&counter))
}
标签:编程,goroutine,sync,var,并发,func,go
From: https://www.cnblogs.com/cheng-sir/p/18004471