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)

猜你喜欢

转载自www.cnblogs.com/zp900704/p/11004858.html