Mutex and rwmutex for concurrent programming in go language

Time:2022-5-9
catalogue
  • 1、 Mutex
    • 1. Introduction to mutex
    • 2. Mutex usage example
  • 2、 Read / write lock rwmutex
    • 1. Rwmutex introduction
    • 2. Rwmutex usage example

In concurrent programming, multipleGoroutineRace conditions may occur when accessing the same memory resource. We need to use appropriate synchronization operations in the critical area to avoid race conditions. Go language provides many synchronization tools. This article will introduce mutexMutEx and read / write lockRWMutexHow to use.

1、 Mutex

1. Introduction to mutex

The synchronization tool of go language is mainly composed ofsync Package provision, mutex(MutexRead write lockRWMutex)Is the method in the sync package.

Mutex lock can be used to protect a critical area and ensure that there is only one at the same timegoroutine Within this critical zone. It mainly includes two operations: locking (lock method) and unlocking (unlock method). Firstly, thegoroutineLock and unlock when leaving.

When using mutex, you should pay attention to the following points:

  • Do not lock the mutex repeatedly, otherwise it will block and cause deadlock(deadlock);
  • To unlock the mutex, this is also to avoid repeated locking;
  • Do not unlock unlocked or unlocked mutexes;
  • Do not pass mutexes directly between multiple functions,sync.MutexType is a value type. When it is passed to a function, a copy will be generated. The operation on the lock in the function will not affect the original lock

In short, a mutually exclusive lock is only used to protect a critical area. Remember to unlock after locking. For each locking operation, there must be and only one corresponding unlocking operation, that is, locking and unlocking should appear in pairs. It is the safest way to use itdeferStatement unlock.

2. Mutex usage example

The following code simulates withdrawal and deposit operations:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package main
 
import (
 "flag"
 "fmt"
 "sync"
)
 
var (
    mutex   sync.Mutex
    balance int
    protecting uint  // Lock or not
    sign = make(chan struct{}, 10) //Channel for waiting for all goroutines
)
 
// save money
func deposit(value int) {
    defer func() {
        sign <- struct{}{}
    }()
 
    if protecting == 1 {
        mutex.Lock()
        defer mutex.Unlock()
    }
 
    fmt.Printf("Balance:% d \ n", balance)
    balance += value
    fmt.Printf("Balance after% d deposit:% d \ n", value, balance)
    fmt.Println()
 
}
 
// Withdraw money
func withdraw(value int) {
    defer func() {
        sign <- struct{}{}
    }()
    
    if protecting == 1 {
        mutex.Lock()
        defer mutex.Unlock()
    }
 
    fmt.Printf("Balance:% d \ n", balance)
    balance -= value
    fmt.Printf("Balance after% d:% d \ n", value, balance)
    fmt.Println()
 
}
 
func main() {
    
    for i:=0; i < 5; i++ {
        go withdraw(500) // Take 500
        go deposit(500)  // Deposit 500
    }
 
    for i := 0; i < 10; i++ {
  <-sign
 }
    fmt.Printf("Current balance:% d \ n", balance)
}
 
func init() {
    balance = 1000 // The initial account balance is 1000
    flag.UintVar(&protecting, "protecting", 0, "Lock or not, 0 means no lock, 1 means lock")
}

In the above code, the channel is used to make the mastergoroutine Wait for othersgoroutine At the end of operation, each subgoroutineSend an element to the channel before the end of the run, the mastergoroutine At the end, the element is received from this channel, and the number of times it is received is the same as that of the sub channelgoroutineThe number is the same. After receiving, it will exit the maingoroutine

The code uses a collaborative process to deposit and withdraw money from an account for many times (5 times),Let’s first look at the situation without locking:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Balance: 1000
Balance after deposit of 500: 1500
 
Balance: 1000
Balance after taking 500: 1000
 
Balance: 1000
Balance after deposit of 500: 1500
 
Balance: 1000
Balance after taking 500: 1000
 
Balance: 1000
Balance after deposit of 500: 1500
 
Balance: 1000
Balance after taking 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 1000
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 1000
Balance after deposit of 500: 1000
 
Current balance: 1000

It can be seen that there is confusion. For example, the balance of 1000 is still 1000 after taking 500 for the second time. This competition for the same resource has competition conditions(Race Condition)。

Let’s look at the implementation results of locking:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after deposit of 500: 1500
 
Balance: 1500
Balance after taking 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Current balance: 1000

It’s normal after locking.

A more detailed mutex is described below:Read / write mutex rwmutex.

2、 Read / write lock rwmutex

1. Rwmutex introduction

Read / write mutexRWMutexIt includes read lock and write lock, which protect the “read operation” and “write operation” of shared resources respectively.sync.RWMutexLock methods and in typesUnlockMethod is used to lock and unlock the write lock respectivelyRLockMethods andRUnlockMethods are used to lock and unlock the read lock respectively.

With mutex, why do I need a read-write lock? Because in many concurrent operations, concurrent reads account for a large proportion and write operations are relatively few. Read write locks can be read concurrently, which can provide service performance.The read-write lock has the following features:

 

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

 

in other words,

  • If a shared resource is protected by a read lock and a write lock, the other resources are lockedgoroutineCannot write. In other words, read-write operations and write operations cannot be executed in parallel, that is, read-write operations are mutually exclusive;
  • When protected by read lock, multiple read operations can be performed at the same time.

When using the read-write lock, you should also pay attention to:

  • Do not unlock the unlocked read-write lock;
  • You cannot unlock a read lock with a write lock
  • You cannot unlock a write lock with a read lock

2. Rwmutex usage example

Rewrite the previous operations of withdrawal and deposit and add the method of querying the balance:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main
 
import (
 "fmt"
 "sync"
)
 
// Account stands for counter.
type account struct {
 num uint         // Number of operations
 balance int   // balance
 rwMu  *sync.RWMutex // Read write lock
}
 
var sign = make(chan struct{}, 15) //Channel for waiting for all goroutines
 
// View balance: use read lock
func (c *account) check() {
 defer func() {
        sign <- struct{}{}
    }()
 c.rwMu.RLock()
 defer c.rwMu.RUnlock()
 fmt.Printf(Balance after% D operations:% d \ n, c.num, c.balance)
}
 
// Save money: write lock
func (c *account) deposit(value int) {
 defer func() {
        sign <- struct{}{}
    }()
    c.rwMu.Lock()
 defer c.rwMu.Unlock()
 
 fmt.Printf("Balance:% d \ n", c.balance)  
 c.num += 1
    c.balance += value
    fmt.Printf("Balance after% d deposit:% d \ n", value, c.balance)
    fmt.Println()
}
 
// Withdrawal: write lock
func (c *account) withdraw(value int) {
    defer func() {
        sign <- struct{}{}
    }()
 c.rwMu.Lock()
 defer c.rwMu.Unlock()  
 fmt.Printf("Balance:% d \ n", c.balance)    
 c.num += 1
    c.balance -= value
 fmt.Printf("Balance after% 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) // Take 500
        go c.deposit(500)  // Deposit 500
  go c.check()
    }
 
    for i := 0; i < 15; i++ {
  <-sign
 }
 fmt.Printf(Balance after% D operations:% d \ n, c.num, c.balance)
 
}

Execution result:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Balance: 1000
Balance after taking 500: 500
 
Balance after one operation: 500
Balance after one operation: 500
Balance after one operation: 500
Balance after one operation: 500
Balance after one operation: 500
Balance: 500
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after deposit of 500: 1500
 
Balance: 1500
Balance after taking 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Balance: 1000
Balance after taking 500: 500
 
Balance: 500
Balance after deposit of 500: 1000
 
Balance after 10 operations: 1000

The difference between read-write lock and mutex lock is that read-write lock separates the read operation and write operation of shared resources, which can realize more complex access control.

Summary:

Read write lock is also a kind of mutex, which is an extension of mutex.Attention should be paid to:

  • Be sure to unlock after locking
  • Do not lock or unlock repeatedly
  • Do not unlock unlocked locks
  • Do not pass mutexes

This is the end of this article about mutex and rwmutex in concurrent programming of go language. For more information about mutex rwmutex in go language, please search previous articles of developepper or continue to browse the relevant articles below. I hope you will support developepper in the future!