SoFunction
Updated on 2025-03-04

Go language concurrent programming mutex Mutex and read-write lock RWMutex

In concurrent programming, multipleGoroutineRace conditions may occur when accessing the same piece of memory resource, and we need to use appropriate synchronization operations in the critical section to avoid race conditions. There are many synchronization tools provided in the Go language, and this article will introduce mutex locks.Mutex and read and write locksRWMutexHow to use it.

1. Mutex

1. Introduction to Mutex

The synchronization tool for Go language is mainly composed ofsync Package provided, mutex (Mutex) and read and write lock (RWMutex) is the method in the sync package.

Mutex can be used to protect a critical area to ensure that there is only one at the same time.goroutine In this critical area. It mainly includes two operations: lock (Lock method) and unlock (Unlock method). First, it is used to enter the critical area.goroutineLock and unlock when leaving.

The following points should be paid attention to when using a mutex:

  • Do not repeatedly lock the mutex, otherwise it will block and may also cause deadlock (deadlock);
  • It is also necessary to unlock the mutex to avoid repeated locking;
  • Do not unlock unlocked or unlocked mutexes;
  • Do not pass mutexes directly between multiple functions.The type belongs to the value type. When passing it to a function, a copy will be generated. The operation of the lock in the function will not affect the original lock.

In short, a mutex is only used to protect one critical area. Remember to unlock it after locking. For each lock operation, there must be only one corresponding unlock operation, that is, locking and unlocking must appear in pairs, and the safest way is used.deferStatement Unlock.

2. Mutex usage example

The following code simulates withdrawal and save money:

package main

import (
 "flag"
 "fmt"
 "sync"
)

var (
    mutex   
    balance int
    protecting uint  // Whether to add lock    sign = make(chan struct{}, 10) //Channel, used to wait for all goroutines)

// Save moneyfunc deposit(value int) {
    defer func() {
        sign <- struct{}{}
    }()

    if protecting == 1 {
        ()
        defer ()
    }

    ("Balance: %d\n", balance)
    balance += value
    ("Balance after saving %d: %d\n", value, balance)
    ()

}

// Withdraw moneyfunc withdraw(value int) {
    defer func() {
        sign <- struct{}{}
    }()
    
    if protecting == 1 {
        ()
        defer ()
    }

    ("Balance: %d\n", balance)
    balance -= value
    ("Balance after %d: %d\n", value, balance)
    ()

}

func main() {
    
    for i:=0; i < 5; i++ {
        go withdraw(500) // Get 500        go deposit(500)  // Save 500    }

    for i := 0; i < 10; i++ {
  <-sign
 }
    ("Current balance: %d\n", balance)
}

func init() {
    balance = 1000 // The initial account balance is 1000    (&protecting, "protecting", 0, "Whether to lock it, 0 means no locking, 1 means locking")
}

In the above code, a channel is used to make the maingoroutine Wait for othergoroutine Running ends, each childgoroutineSend an element to the channel before the run ends,goroutine At the end, the element is received from this channel, the number of times is received and the subgoroutineThe number is the same. After receiving it, it will exit the maingoroutine

The code uses coroutines to implement the operation of saving and withdrawing money on an account multiple times (5 times).Let’s first look at the situation where no lock is added:

Balance: 1000
live 500 后的Balance: 1500

Balance: 1000
Pick 500 后的Balance: 1000

Balance: 1000
live 500 后的Balance: 1500

Balance: 1000
Pick 500 后的Balance: 1000

Balance: 1000
live 500 后的Balance: 1500

Balance: 1000
Pick 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 1000
live 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 1000
live 500 后的Balance: 1000

当前Balance: 1000

You can see chaos, such as the second balance of 1,000 is withdrawn by 500 or 1,000. This competition for the same resource has a competitive condition (Race Condition)。

Let’s see the execution results of locking:

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

Balance: 1000
live 500 后的Balance: 1500

Balance: 1500
Pick 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

当前Balance: 1000

It's normal after locking.

The following is a more detailed mutex lock:Read/write mutex RWMutex.

2. Read and write lock RWMutex

1. Introduction to RWMutex

Read/write mutex lockRWMutexIt contains read locks and write locks, which protect the "read operations" and "write operations" of shared resources respectively.Lock methods andUnlockMethods are used to lock and unlock the write lock, and itsRLockMethods andRUnlockThe methods are used to lock and unlock the read lock respectively.

With Mutex, why do you still need to read and write locks? Because in many concurrent operations, concurrent read accounts for a large proportion and write operations are relatively small, read and write locks can be read concurrently, which can provide service performance.A read and write lock has the following characteristics:

Read and write lock Read lock Write lock
Read lock Yes No
Write lock No No

That is to say,

  • If a shared resource is protected by read and write locks, othergoroutineCannot perform write operations. In other words, read and write operations and write operations cannot be executed in parallel, that is, read and write mutually exclusive;
  • When protected by a read lock, multiple read operations can be performed simultaneously.

When using read and write locks, you also need to pay attention to:

  • Do not unlock unlocked read and write locks;
  • Cannot use write lock to unlock for read locks
  • Cannot use read lock to unlock for write locks

2. RWMutex usage example

Rewrite the previous withdrawal and deposit operations and add the method to query the balance:

package main

import (
 "fmt"
 "sync"
)

// account represents the counter.type account struct {
 num uint         // Number of operations balance int   // Balance rwMu  * // Read and write lock}

var sign = make(chan struct{}, 15) //Channel, used to wait for all goroutines
// View balance: Use read lockfunc (c *account) check() {
 defer func() {
        sign <- struct{}{}
    }()
 ()
 defer ()
 ("The balance after %d operations: %d\n", , )
}

// Save money: write lockfunc (c *account) deposit(value int) {
 defer func() {
        sign <- struct{}{}
    }()
    ()
 defer () 

 ("Balance: %d\n", )   
  += 1
     += value
    ("Balance after saving %d: %d\n", value, )
    () 
}

// Withdraw money: write lockfunc (c *account) withdraw(value int) {
    defer func() {
        sign <- struct{}{}
    }()
 ()
 defer ()   
 ("Balance: %d\n", )     
  += 1
     -= value
 ("Balance after %d: %d\n", value, )
    ()  
}


func main() {
 c := account{0, 1000, new()}

 for i:=0; i < 5; i++ {
        go (500) // Get 500        go (500)  // Save 500  go ()
    }

    for i := 0; i < 15; i++ {
  <-sign
 }
 ("The balance after %d operations: %d\n", , )

}

Execution results:

Balance: 1000
Pick 500 后的Balance: 500

1 次操作后的Balance: 500
1 次操作后的Balance: 500
1 次操作后的Balance: 500
1 次操作后的Balance: 500
1 次操作后的Balance: 500
Balance: 500
live 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

Balance: 1000
live 500 后的Balance: 1500

Balance: 1500
Pick 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

Balance: 1000
Pick 500 后的Balance: 500

Balance: 500
live 500 后的Balance: 1000

10 次操作后的Balance: 1000

The difference between read and write locks and mutex locks is that read and write locks separate read operations and write operations on shared resources, which can achieve more complex access control.

Summarize:

A read and write lock is also a mutex, which is an extension of a mutex.Pay attention to when using:

  • Must unlock after locking
  • Do not repeatedly lock or unlock
  • Unlocked unlocked locks
  • Don't pass mutex locks

This is the article about the mutex Mutex and read-write lock RWMutex for Go concurrent programming. For more related Go language Mutex RWMutex, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!