云海的博客
首页
  • 接口
  • 数组
  • slice
  • map
  • 指针
  • 反射
  • Context
  • sync.map
  • 锁
  • 类型和类型指针分析
  • recover
  • 从零实现RPC框架
  • make和new区别
  • channel
  • sync.Once
  • sync.Pool
  • protobuf
  • MongoDB pkg源码-findone
  • MyBatis
  • Maven
  • 解析Laravel框架—路由处理
  • PHP(客户端)与 Golang(服务端)使用grpc+protobuf 通信
  • JAVA(客户端)与 Golang(服务端) 使用grpc+protobuf通信
  • Docker使用笔记-常用命令
  • Docker使用笔记-容器间通讯
  • Docker使用笔记-搭建Redis集群
  • Docker使用笔记-镜像多阶段构建
  • Kubernetes部署golang服务
  • Linux常用命令
  • Docker安装Prometheus与Grafana
  • Protobuf
  • TCP抓包
  • 概述-《TCP/IP详解》读书笔记
  • 索引
  • 事务隔离级别
  • 常识
  • 每日一题(1)
  • 每日一题(2)
  • 每日一题(3)
  • 每日一题(4)
关于
GitHub (opens new window)

云海

服务端研发
首页
  • 接口
  • 数组
  • slice
  • map
  • 指针
  • 反射
  • Context
  • sync.map
  • 锁
  • 类型和类型指针分析
  • recover
  • 从零实现RPC框架
  • make和new区别
  • channel
  • sync.Once
  • sync.Pool
  • protobuf
  • MongoDB pkg源码-findone
  • MyBatis
  • Maven
  • 解析Laravel框架—路由处理
  • PHP(客户端)与 Golang(服务端)使用grpc+protobuf 通信
  • JAVA(客户端)与 Golang(服务端) 使用grpc+protobuf通信
  • Docker使用笔记-常用命令
  • Docker使用笔记-容器间通讯
  • Docker使用笔记-搭建Redis集群
  • Docker使用笔记-镜像多阶段构建
  • Kubernetes部署golang服务
  • Linux常用命令
  • Docker安装Prometheus与Grafana
  • Protobuf
  • TCP抓包
  • 概述-《TCP/IP详解》读书笔记
  • 索引
  • 事务隔离级别
  • 常识
  • 每日一题(1)
  • 每日一题(2)
  • 每日一题(3)
  • 每日一题(4)
关于
GitHub (opens new window)
  • 接口
  • 数组
  • slice
  • map
  • 反射
  • sync.Pool
  • net包笔记
  • net-rpc分析
  • 指针
  • 数组排序
  • Context
  • sync.map
  • 锁
    • recover
    • 泛型
    • 类型和类型指针分析
    • make和new区别
    • channel
    • sync.Once
    • protobuf
    • GoLand debug(1)
    • 从零实现RPC框架
    • 从零开始学Go Origin框架
    • MongoDB pkg源码-findone
    • Golang
    云海
    2018-07-15
    目录

    锁

    # 概述

    当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序(这通常被称为资源竞争,指不同线程对同一变量使用的竞争)显然这无法让人容忍,那我们该如何解决这个问题?

    经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时(临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。

    map类型是非线程安全的,当并行访问一个共享的map类型的数据,map数据将会出错。

    在Go语言中这种锁的机制是通过sync包中Mutex来实现的。sync来源“synchronized”一词,这意味着线程将有序的对同一变量进行访问。

    # 互斥锁

    sync.Mutex 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。

    假设info是一个需要上锁的放在共享内存中的变量。通过包含Mutex来实现的一个典型例子如下:

    import "sync"
    
    type Info struct {
        mu sync.Mutex
        Str string 
    }
    
    1
    2
    3
    4
    5
    6

    如果一个函数想要改变这个变量可以这样写:

    func update(info *Info) {
        info.mu.Lock()
        info.Str = ""
        info.mu.Unlock()
    }
    
    1
    2
    3
    4
    5

    还有一个很有用的例子是通过Mutex来实现一个可以上锁的共享缓冲器:

    type SyncedBuffer struct {
        lock   sync.Mutex
        buffer bytes.Buffer
    }
    
    1
    2
    3
    4
    var x int64
    var wg sync.WaitGroup
    var lock sync.Mutex
    
    func add() {
      for i := 0; i < 5000; i++ {
        lock.Lock()
        x = + 1
        lock.Unlock()
      }
      wg.Done()
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 读写互斥锁

    在sync包中还有一个RWMutex锁:他能通过RLock()来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用Lock()将和普通的Mutex作用相同。

    var (
      lock sync.Mutex
      rwlock sync.RWMutex
    )
    
    func read() {
      rwlock.RLock()
      x := + 1
      rwlock.RUnLock()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Sync.Once

    包中还有一个方便的Once类型变量的方法once.Do(call),这个方法确保被调用函数只能被调用一次。 相对简单的情况下,通过使用sync包可以解决同一时间只能一个线程访问变量或map类型数据的问题。如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过goroutines和Channels 来解决问题,这是在Go语言中所提倡用来实现并发的技术。

    # 实现可重入锁
    type RecursiveMutex struct {
    	sync.Mutex
    	owner     int64 //当前持有锁的goroutine id
    	recursion int32 //重入次数
    }
    
    func (m *RecursiveMutex) Lock() {
    	gid := goid.Get()
    
    	if atomic.LoadInt64(&m.owner) == gid {
    		m.recursion++
    		return
    	}
    	m.Mutex.Lock()
    	atomic.StoreInt64(&m.owner, gid)
    	m.recursion = 1
    }
    
    func (m *RecursiveMutex) Unlock() {
    	gid := goid.Get()
    	if atomic.LoadInt64(&m.owner) != gid {
    		panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid))
    	}
    	m.recursion--
    	if m.recursion != 0 {
    		return
    	}
    	atomic.StoreInt64(&m.owner, -1)
    	m.Mutex.Unlock()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    # TryLock

    在某些情况下,我们希望在获取锁失败时,并不想阻塞等待,而是希望进入其它逻辑。这时候,就需要TryLock方法,此方法是在go1.18版本,被官方加入。当调用 TryLock()时,该函数仅返回true或者false,代表是否加锁成功。

    上次更新: 2023/06/05
    sync.map
    recover

    ← sync.map recover→

    最近更新
    01
    函数
    04-11
    02
    面试题
    04-11
    03
    EFK日志收集系统单机版
    08-18
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式