休闲
当前位置:首页 >> 休闲 >> 文章正文

Go言语并发编程之互斥锁Mutex和读写锁RWMutex

发布时间:2026-07-20 19:30:50点击:57376

在并发编程中  ,言语多个Goroutine访谒不合块内存成本时大年夜大年夜约会展示竞态前提 ,并发编程我们需求在临界区中独霸安妥的斥锁同步独霸来以阻拦竞态前提 。Go 言语中供给了良多同步对象  ,读写本文将引见互斥锁Mutex和读写锁RWMutex的言语独霸编制 。

1、并发编程互斥锁Mutex

1、斥锁Mutex引见

Go 言语的读写同步对象次要由 sync 包供给 ,互斥锁 (Mutex) 与读写锁 (RWMutex) 就是言语sync 包中的编制 。

互斥锁可以用来呵护一个临界区,并发编程担保不应时辰只需一个 goroutine 处于该临界区内。斥锁次要包含锁定(Lock编制)和解锁(Unlock编制)两个独霸,读写起首对进进临界区的言语goroutine举办锁定,离开时举办解锁 。并发编程

独霸互斥锁 (Mutex)时要寄看以下几点  :

  • 不要几回锁定互斥锁,斥锁不然会梗阻 ,也大年夜大年夜约会招致作古锁(deadlock);
  • 要对互斥锁举办解锁,这也是为了不几回锁定;
  • 不要对未锁定或已解锁的互斥锁解锁;
  • 不要在多个函数之间直接传递互斥锁,sync.Mutex圭表类型属于值圭表类型 ,将它传给一个函数时 ,会产生发火一个副本 ,在函数中对锁的独霸不会影响原锁

总之,一个互斥锁只用来呵护一个临界区 ,加锁跋文得解锁 ,对每个锁定独霸,都要有且只需一个对应的解锁独霸,也就是加锁和解锁要成对展示 ,最保险的做法时独霸 defer语句 解锁。

2 、Mutex独霸实例

上面的代码模仿取钱和存钱独霸:

package mainimport ( "flag" "fmt" "sync")var (    mutex   sync.Mutex    balance int    protecting uint  // 可否加锁    sign = make(chan struct{ }, 10) //通道,用于等待全数goroutine)// 存钱func deposit(value int) {     defer func() {         sign <- struct{ }{ }    }()    if protecting == 1 {         mutex.Lock()        defer mutex.Unlock()    }    fmt.Printf("余额: %d\n", balance)    balance += value    fmt.Printf("存 %d 后的余额: %d\n", value, balance)    fmt.Println()}// 取钱func withdraw(value int) {     defer func() {         sign <- struct{ }{ }    }()        if protecting == 1 {         mutex.Lock()        defer mutex.Unlock()    }    fmt.Printf("余额: %d\n", balance)    balance -= value    fmt.Printf("取 %d 后的余额: %d\n", value, balance)    fmt.Println()}func main() {         for i:=0; i < 5; i++ {         go withdraw(500) // 取500        go deposit(500)  // 存500    }    for i := 0; i < 10; i++ {   <-sign }    fmt.Printf("往后余额: %d\n", balance)}func init() {     balance = 1000 // 初始账户余额为1000    flag.UintVar(&protecting, "protecting", 0, "可否加锁,0展示不加锁,1展示加锁")}

上面的代码中,独霸了通道来让主 goroutine 等待其他 goroutine 运转停止 ,每个子goroutine在运转停止之前向通道发送一个元素,主 goroutine 在末尾从这个通道领受元素  ,领受次数与子goroutine个数不异。领受完后就会介入主goroutine。

代码独霸协程完成多次(5次)对一个账户举办存钱和取钱的独霸 ,先来看不加锁的气候:

余额: 1000存 500 后的余额: 1500余额: 1000取 500 后的余额: 1000余额: 1000存 500 后的余额: 1500余额: 1000取 500 后的余额: 1000余额: 1000存 500 后的余额: 1500余额: 1000取 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 1000存 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 1000存 500 后的余额: 1000往后余额: 1000

可以看到展示了混乱  ,比如第二次1000的余额取500后仍是1000,这类对不合成本的竞争展示了竞态前提(Race Condition) 。

上面来看加锁的奉行下场:

余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 1000余额: 1000存 500 后的余额: 1500余额: 1500取 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 1000往后余额: 1000

加锁后就正常了 。

上面引见更细化的互斥锁 :读/写互斥锁RWMutex 。

2 、读写锁RWMutex

1 、RWMutex引见

读/写互斥锁RWMutex包含了读锁和写锁,分袂对共享成本的“读独霸”和“写独霸”举办呵护。sync.RWMutex圭表类型中的Lock编制和Unlock编制分袂用于对写锁举办锁定和解锁 ,而它的RLock编制和RUnlock编制例分袂用于对读锁举办锁定和解锁。

有了互斥锁Mutex ,为甚么还需求读写锁呢 ?因为在良多并发独霸中,并发读取占比很除夜 ,写独霸绝对较少 ,读写锁可以并发读取,多么可以供给处事功用 。读写锁具有以下特点 :

读写锁读锁写锁
读锁YesNo
写锁NoNo

也就是说 ,

  • 假定某个共享成本遭到读锁和写锁呵护时 ,此外goroutine不克不及举办写独霸 。换句话说就是读写独霸和写写独霸不克不及并行奉行,也就是读写互斥;
  • 受读锁呵护时 ,可以同时举办多个读独霸。

在独霸读写锁时,还需求寄看 :

  • 不要对未锁定的读写锁解锁;
  • 对读锁不克不及独霸写锁解锁
  • 对写锁不克不及独霸读锁解锁

2 、RWMutex独霸实例

改写后面的取钱和存钱独霸,添加查询余额的编制 :

package mainimport ( "fmt" "sync")// account 代表计数器。type account struct {  num uint         // 独霸次数 balance int   // 余额 rwMu  *sync.RWMutex // 读写锁}var sign = make(chan struct{ }, 15) //通道,用于等待全数goroutine// 搜检余额:独霸读锁func (c *account) check() {  defer func() {         sign <- struct{ }{ }    }() c.rwMu.RLock() defer c.rwMu.RUnlock() fmt.Printf("%d 次独霸后的余额: %d\n", c.num, c.balance)}// 存钱:写锁func (c *account) deposit(value int) {  defer func() {         sign <- struct{ }{ }    }()    c.rwMu.Lock() defer c.rwMu.Unlock()  fmt.Printf("余额: %d\n", c.balance)    c.num += 1    c.balance += value    fmt.Printf("存 %d 后的余额: %d\n", value, c.balance)    fmt.Println() }// 取钱
	:写锁func (c *account) withdraw(value int) {     defer func() {         sign <- struct{ }{ }    }() c.rwMu.Lock() defer c.rwMu.Unlock()    fmt.Printf("余额: %d\n", c.balance)      c.num += 1    c.balance -= value fmt.Printf("取 %d 后的余额: %d\n", value, c.balance)    fmt.Println()  }func main() {  c := account{ 0, 1000, new(sync.RWMutex)} for i:=0; i < 5; i++ {         go c.withdraw(500) // 取500        go c.deposit(500)  // 存500  go c.check()    }    for i := 0; i < 15; i++ {   <-sign } fmt.Printf("%d 次独霸后的余额: %d\n", c.num, c.balance)}

奉行下场 :

余额: 1000取 500 后的余额: 5001 次独霸后的余额: 5001 次独霸后的余额: 5001 次独霸后的余额: 5001 次独霸后的余额: 5001 次独霸后的余额: 500余额: 500存 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 1000余额: 1000存 500 后的余额: 1500余额: 1500取 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 1000余额: 1000取 500 后的余额: 500余额: 500存 500 后的余额: 100010 次独霸后的余额: 1000

读写锁和互斥锁的不合的处地址于读写锁把对共享成本的读独霸和写独霸离开了 ,可以完成更宏壮的访谒独霸 。

总结:

读写锁也是一种互斥锁 ,它是互斥锁的扩大年夜大年夜大年夜大年夜 。在独霸时需求寄看 :

  • 加锁后必定要解锁
  • 不要几回加锁或解锁
  • 不解锁未锁定的锁
  • 不要传递互斥锁

到此这篇关于Go言语并发编程之互斥锁Mutex和读写锁RWMutex的文章就引见到这了,更多相干Go言语 Mutex RWMutex内容请搜刮完竣下载之前的文章或延续不雅不雅不雅不雅鉴赏上面的相干文章希看大年夜师往后多多支撑完竣下载!

相关装修文章Related Articles

热门阅读文章

最新装修文章