前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go :标准库Sync简介与实践

Go :标准库Sync简介与实践

作者头像
Freedom123
发布2024-03-29 14:02:52
940
发布2024-03-29 14:02:52
举报
文章被收录于专栏:DevOpsDevOps

简介

Golang sync包提供了基础的异步操作方法,包括互斥锁Mutex,执行一次Once和并发等待组WaitGroup。

  • Mutex: 互斥锁
  • RWMutex:读写锁
  • WaitGroup:并发等待组
  • Once:执行一次
  • Cond:信号量
  • Pool:临时对象池
  • Map:自带锁的map

一、单实例

设计模式(Design pattern),提供了在软件开发过程中面临的一些问题的最佳解决方案。主要分创建型模式、结构型模式和行为型模式。

首先来看创建型模式(Creational Patterns),它提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。单例模式属于创建型模式,单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式又分为饿汉方式和懒汉方式。

  • 饿汉式,从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。
  • 懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。

对于golang,饿汉方式指全局的单例实例在包被加载时创建,而懒汉方式指全局的单例实例在第一次被使用时创建。除了自己手写饿汉方式和懒汉方式,在 Go 开发中,还有一种更优雅的实现方式(使用sync包的once.Do)

sync.Once 指的是只执行一次的对象实现,常用来控制某些函数只能被调用一次。sync.Once的使用场景例如单例模式、系统初始化。例如并发情况下多次调用channel的close会导致panic,解决这个问题我们可以使用sync.Once来保证close只会被执行一次。sync.Once的结构如下所示,只有一个函数。使用变量done来记录函数的执行状态,使用sync.Mutex和sync.atomic来保证线程安全的读取done。

代码语言:javascript
复制
type Once struct {
	m    Mutex     #互斥锁
	done uint32    #执行状态
}

func (o *Once) Do(f func())

举个例子,1000个并发协程情况下只有一个协程会执行到fmt.Printf,多次执行的情况下输出的内容还不一样,因为这取决于哪个协程先调用到该匿名函数。

代码语言:javascript
复制
func main() {
     once := &sync.Once{}

     for i := 0; i < 1000; i++ {
	 go func(idx int) {
	    once.Do(func() {
		time.Sleep(1 * time.Second)
		fmt.Printf("hello world index: %d", idx)
	    })
	 }(i)
     }

     time.Sleep(5 * time.Second)
}

demo:使用sync包的once.Do,实现单例模式

代码语言:javascript
复制
package singleton

import (
    "sync"
)

type singleton struct {
}

var ins *singleton
var once sync.Once

func GetInsOr() *singleton {
    once.Do(func() {
        ins = &singleton{}
    })
    return ins
}

使用noce.Do调用时方法内执行代码只执行一次。输出结果:

代码语言:javascript
复制
Create Obj
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8
6c7df8

二、读写锁

在读多写少的环境中,可以优先使用读写互斥锁(sync.RWMutex),它比互斥锁更加高效。sync 包中的 RWMutex 提供了读写互斥锁的封装。读写锁分为:读锁和写锁:

  • 如果设置了一个写锁,那么其它读的线程以及写的线程都拿不到锁,这个时候,与互斥锁的功能相同
  • 如果设置了一个读锁,那么其它写的线程是拿不到锁的,但是其它读的线程是可以拿到锁

通过设置写锁,同样可以实现数据的一致性:

代码语言:javascript
复制
package main
import ("fmt"
    "sync"
)
 
var (
    count int
    rwLock sync.RWMutex
)
 
func main() {
    for i := 0; i < 2; i++ {
        go func() {
            for i := 1000000; i > 0; i-- {
                rwLock.Lock()
                count ++
                rwLock.Unlock()
            }
            fmt.Println(count)
        }()
    }
 
    fmt.Scanf("\n")  //等待子线程全部结束
}
代码语言:javascript
复制
1968637
2000000
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 一、单实例
  • 二、读写锁
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档