golang中的Mutex和RWMutex

????sync中有两种锁 Mutex和RWMutex,Mutex的读和写必须都进行排队,只能一个完成了在进行下一个,RWMutex读可以并行,写只能一个一个进行,当有读时,需要所有的读全部关闭后才能进行写操作,有写
时,需要等写操作完成了才能进行读操作(读并行,写单一)。(sync中有一个map对象是线程安全的,默认的map不是线程安全的)。

Mutex

先看下面的代码:

package main

import (
    "fmt"
    "sync"
    "time"
)
func main() {
    go setm(1)
    time.Sleep(time.Millisecond * 1)
    go setm(2)
    time.Sleep(time.Millisecond * 1)
    go setm(3)
    time.Sleep(time.Millisecond * 1)
    go setm(4)
    time.Sleep(time.Millisecond * 1)
    time.Sleep(time.Minute)
}

运行结果:

---- 2019-06-11 15:55:15.053098 +0800 CST m=+0.001952900 setm 1
---- 2019-06-11 15:55:15.0630003 +0800 CST m=+0.011855100 setm1 1
---- 2019-06-11 15:55:15.0550915 +0800 CST m=+0.003946400 setm 2
---- 2019-06-11 15:55:15.0589531 +0800 CST m=+0.007807900 setm 4
---- 2019-06-11 15:55:15.0569997 +0800 CST m=+0.005854500 setm 3
---- 2019-06-11 15:55:16.0637775 +0800 CST m=+1.012632400 setm1 2
---- 2019-06-11 15:55:17.0644006 +0800 CST m=+2.013255400 setm1 4
---- 2019-06-11 15:55:18.0651814 +0800 CST m=+3.014036200 setm1 3

从上面的输出可以看到,lock的后的代码是阻塞的,像队列一样,先进去,先出来,一个接一个完成。

RWMutex

这是一个读写锁,可以并发的读,单一的写,主要用于读多写少的情况下。

先看下面的一段代码:

package main

import (
    "fmt"
    "sync"
    "time"
)

var data = map[string]int{
    "a": 0,
}

var lock sync.RWMutex

func main() {

    go get(1)
    time.Sleep(time.Millisecond * 1)
    go get(2)
    time.Sleep(time.Millisecond * 1)
    go set(1)
    time.Sleep(time.Millisecond * 1)
    go set(2)
    time.Sleep(time.Millisecond * 1)
    go get(3)
    time.Sleep(time.Millisecond * 1)
    go set(3)
    time.Sleep(time.Millisecond * 1)
    go set(4)
    time.Sleep(time.Millisecond * 1)
    go get(4)
    time.Sleep(time.Minute)
}

func get(index int) {
    fmt.Println("----", time.Now(), "get", index, data["a"])
    lock.RLock()
    defer lock.RUnlock()
    fmt.Println(time.Now(), "get", data["a"])
    time.Sleep(time.Second * 1)
}

func set(a int) {
    fmt.Println("----", time.Now(), "set", a, data["a"])
    lock.Lock()
    defer lock.Unlock()
    fmt.Println(time.Now(), "set", a)
    data["a"] = a
    time.Sleep(time.Second * 1)
}

运行结果:

---- 2019-06-11 15:59:54.1919927 +0800 CST m=+0.002940900 get 1 0
2019-06-11 15:59:54.2015089 +0800 CST m=+0.012457100 get 0
---- 2019-06-11 15:59:54.1936932 +0800 CST m=+0.004641400 get 2 0
2019-06-11 15:59:54.2015089 +0800 CST m=+0.012457100 get 0
---- 2019-06-11 15:59:54.1956461 +0800 CST m=+0.006594300 set 1 0
---- 2019-06-11 15:59:54.1966613 +0800 CST m=+0.007609600 set 2 0
---- 2019-06-11 15:59:54.1985726 +0800 CST m=+0.009520800 get 3 0
---- 2019-06-11 15:59:54.200525 +0800 CST m=+0.011473200 set 3 0
---- 2019-06-11 15:59:54.2023586 +0800 CST m=+0.013306800 set 4 0
---- 2019-06-11 15:59:54.2036319 +0800 CST m=+0.014580200 get 4 0
2019-06-11 15:59:55.2027123 +0800 CST m=+1.013660500 set 1
2019-06-11 15:59:56.202904 +0800 CST m=+2.013852200 get 1
2019-06-11 15:59:56.202904 +0800 CST m=+2.013852200 get 1
2019-06-11 15:59:57.2032109 +0800 CST m=+3.014159100 set 2
2019-06-11 15:59:58.2037928 +0800 CST m=+4.014741000 set 3
2019-06-11 15:59:59.2046672 +0800 CST m=+5.015615500 set 4

????从运行结果可以看到,同时可以有多个读,但是只能有一个写,当有写时,读会阻塞。
从上面的运行结果看,有一点很奇怪,明明是两个连写的写操作,在进行读操作,但是,只进行了一个写操作后,就进行了所有的读操作然后在进行了剩余的写操作。

我们看一下golang中对RWMutex的实现:

/*读写锁的定义*/
type RWMutex struct {
    w           Mutex  // held if there are pending writers
    writerSem   uint32 // semaphore for writers to wait for completing readers
    readerSem   uint32 // semaphore for readers to wait for completing writers
    readerCount int32  // number of pending readers
    readerWait  int32  // number of departing readers
}

func (rw *RWMutex) RLock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }
    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        // A writer is pending, wait for it.
        runtime_Semacquire(&rw.readerSem)
    }
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
    }
}

func (rw *RWMutex) RUnlock() {
    if race.Enabled {
        _ = rw.w.state
        race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }
    if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
        if r+1 == 0 || r+1 == -rwmutexMaxReaders {
            race.Enable()
            throw("sync: RUnlock of unlocked RWMutex")
        }
        // A writer is pending.
        if atomic.AddInt32(&rw.readerWait, -1) == 0 {
            // The last reader unblocks the writer.
            runtime_Semrelease(&rw.writerSem, false)
        }
    }
    if race.Enabled {
        race.Enable()
    }
}

func (rw *RWMutex) Lock() {
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }
    // First, resolve competition with other writers.
    rw.w.Lock()
    // Announce to readers there is a pending writer.
    r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    // Wait for active readers.
    if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
        runtime_Semacquire(&rw.writerSem)
    }
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
        race.Acquire(unsafe.Pointer(&rw.writerSem))
    }
}

func (rw *RWMutex) Unlock() {
    if race.Enabled {
        _ = rw.w.state
        race.Release(unsafe.Pointer(&rw.readerSem))
        race.Release(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }

    // Announce to readers there is no active writer.
    r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    if r >= rwmutexMaxReaders {
        race.Enable()
        throw("sync: Unlock of unlocked RWMutex")
    }
    // Unblock blocked readers, if any.
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem, false)
    }
    // Allow other writers to proceed.
    rw.w.Unlock()
    if race.Enabled {
        race.Enable()
    }
}

读锁定(RLock):
当存在写操作时,进行阻塞,等待写信号完成然后进行读操作。如果没有写操作,则执读

读解锁(RUnlock):
读解锁:读解锁,触发读完成操作

写锁定(Lock):
先进行lock操作,进行代码阻塞,然后判断是否存在读操作,当存在读操作时,进行等待,等所有的读操作完成后在进行剩余的操作 写操作完成后,出写操作完成

写解锁(Unlock):
先出发写完成操作,然后在执行写解锁

对上面的代码进行分析:
先执行两次读取操作,此时,读还没有完成。由于是并发,此时执行写操作,代码锁定,等待读操作完成,此时,读操作数量是2,然后执行set(2)操作,由于lock了,此时代码阻塞,只能等set(1)解锁。后面的读取操作,由于有前置的写操作,需要等待写操作完成,由于set(1)执行完,解锁前先出发runtime_Semrelease,此时,执行读操作,读操作完成后,执行解锁操作,执行剩余的赋值操作

    go get(1) //获取数据,read+1=1
    time.Sleep(time.Millisecond * 1)
    go get(2) //获取数据, read+1=2
    time.Sleep(time.Millisecond * 1)
    go set(1) //锁定 等待read完成:get(1)、get(2)
    time.Sleep(time.Millisecond * 1)
    go set(2) //代码锁定 等待set(1)完成
    time.Sleep(time.Millisecond * 1)
    go get(3) //获取数据,等待写操作完成即:set(1)完成,
    time.Sleep(time.Millisecond * 1)
    go set(3) //设置数据,等待set(2) 完成
    time.Sleep(time.Millisecond * 1)
    go set(4) //设置数据,等待set(2) 完成
    time.Sleep(time.Millisecond * 1)
    go get(4) //获取数据,等待写操作完成即:set(1)完成,
    time.Sleep(time.Minute)

原文地址:https://www.cnblogs.com/zp900704/p/11004858.html

时间: 2024-11-05 18:33:19

golang中的Mutex和RWMutex的相关文章

golang 中 sync.Mutex 和 sync.RWMutex

介绍 golang 中的 sync 包实现了两种锁: Mutex:互斥锁 RWMutex:读写锁,RWMutex 基于 Mutex 实现 Mutex(互斥锁) Mutex 为互斥锁,Lock() 加锁,Unlock() 解锁 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁 在 Lock() 之前使用 Unlock() 会导

golang中锁mutex的实现

golang中的锁是通过CAS原子操作实现的,Mutex结构如下: type Mutex struct { state int32 sema  uint32 } //state表示锁当前状态,每个位都有意义,零值表示未上锁 //sema用做信号量,通过PV操作从等待队列中阻塞/唤醒goroutine,等待锁的goroutine会挂到等待队列中,并且陷入睡眠不被调度,unlock锁时才唤醒.具体在sync/mutex.go Lock函数实现中. 插播一下sema 虽然在Mutex中就是一个整形字段

Golang中使用log(二):Golang 标准库log的实现

前一篇文章我们看到了Golang标准库中log模块的使用,那么它是如何实现的呢?下面我从log.Logger开始逐步分析其实现. 其源码可以参考官方地址 1.Logger结构 首先来看下类型Logger的定义: type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix to write at beginning of each

不得不知道的golang之sync.Mutex互斥锁源码分析

针对Golang 1.9的sync.Mutex进行分析,与Golang 1.10基本一样除了将panic改为了throw之外其他的都一样.源代码位置:sync\mutex.go.可以看到注释如下: Mutex can be in 2 modes of operations: normal and starvation. In normal mode waiters are queued in FIFO order, but a woken up waiter does not own the m

Golang中多用途的defer

defer顾名思义就是延迟执行,那么defer在Golang中该如何使用以及何时使用呢? A "defer" statement invokes a function whose executionis deferred to the moment the surrounding function returns, Golang的官方时这么定义的. 1.那么在什么情况下会调用defer延迟过的函数呢? 从文档中可以知道主要有两种情况: 当函数执行了return 语句后 当函数处于pan

Golang中使用log(一):Golang 标准库提供的Log

Golang的标准库提供了log的机制,但是该模块的功能较为简单(看似简单,其实他有他的设计思路).不过比手写fmt. Printxxx还是强很多的.至少在输出的位置做了线程安全的保护.其官方手册见Golang log (天朝的墙大家懂的).这里给出一个简单使用的例子: package main import ( "log" ) func main(){ log.Fatal("Come with fatal,exit with 1 \n") } 编译运行后,会看到程

Go_18: Golang 中三种读取文件发放性能对比

Golang 中读取文件大概有三种方法,分别为: 1. 通过原生态 io 包中的 read 方法进行读取 2. 通过 io/ioutil 包提供的 read 方法进行读取 3. 通过 bufio 包提供的 read 方法进行读取 下面通过代码来验证这三种方式的读取性能,并总结出我们平时应该使用的方案,以便我们可以写出最优代码: package main import ( "os" "io" "bufio" "io/ioutil"

Go_14:GoLang中 json、map、struct 之间的相互转化

1. golang 中 json 转 struct <1. 使用 json.Unmarshal 时,结构体的每一项必须是导出项(import field).也就是说结构体的 key 对应的首字母必须为大写.请看下面的例子: package commontest import ( "testing" "encoding/json" ) type Person struct { name string age int } func TestStruct2Json(

golang中使用selenium进行爬虫

selenium本来是用来做自动测试,但是因为可以模拟浏览器操作,所以也可以用来做爬虫(尤其是一些比较变态登陆网站,又不会模拟登陆的),只是速度会比较慢. 转载请注明出处:http://www.cnblogs.com/SSSR/p/6390229.html 经验总结: 1.火狐浏览器在运行较长时间后,会导致内存泄露,但是Google浏览器不会,所以如果长时间运行还是使用Google浏览器比较好. 2.截图方面选择火狐浏览器,Google浏览器无法截全部页面,即使设置了页面大小也不行. 3.Fir