SoFunction
Updated on 2025-03-01

Go language concurrent programming mutex details

1. Mutex

1.1 Introduction to Mutex

Go The language synchronization tool is mainly composed ofsync Package provided, mutex (Mutex) and read and write lock (RWMutex) that issync Method in 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. Mainly including locking (LockMethod) and unlock (UnlockMethod) Two operations, first of all, thegoroutineLock 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.

1.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 main goroutine wait for the other goroutines to run. Each child goroutine sends an element to the channel before the run ends.goroutine At the end, the element received from this channel is received the same number of sub-goroutines. After receiving, the main goroutine will be exited.

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
Balance after depositing 500: 1500

Balance: 1000
Balance after withdrawing 500: 1000

Balance: 1000
Balance after depositing 500: 1500

Balance: 1000
Balance after withdrawing 500: 1000

Balance: 1000
Balance after depositing 500: 1500

Balance: 1000
Balance after withdrawing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 1000
Balance after depositing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 1000
Balance after depositing 500: 1000

Current balance: 1000

You can see chaos, such as the second time the balance of 1,000 is withdrawn by 500 or 1,000, and there is a race condition for this competition for the same resource.

Let’s see the execution results of locking:

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Balance: 1000
Balance after depositing 500: 1500

Balance: 1500
Balance after withdrawing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Current balance: 1000
It's normal after locking.

The following is a more detailed mutex: Read/write mutexRWMutex

2. Read and write lock RWMutex

2.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.of typeLockMethods 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 lockMutex, why do you still need a read and write lock? 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.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
Balance after withdrawing 500: 500

Balance after 1 operation: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance after 1 operation: 500
Balance: 500
Balance after depositing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Balance: 1000
Balance after depositing 500: 1500

Balance: 1500
Balance after withdrawing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Balance: 1000
Balance after withdrawing 500: 500

Balance: 500
Balance after depositing 500: 1000

Balance after 10 operations: 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 details of Go concurrent programming and mutex locks. For more related Go concurrent programming and mutex locks, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!