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, multipleGoroutine
Race 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 mutexMut
Ex and read / write lockRWMutex
How to use.
1、 Mutex
1. Introduction to mutex
The synchronization tool of go language is mainly composed ofsync
Package provision, mutex(Mutex
)Read write lock(RWMutex
)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, thegoroutine
Lock 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.Mutex
Type 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 itdefer
Statement 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 subgoroutine
Send 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 channelgoroutine
The 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 mutexRWMutex
It includes read lock and write lock, which protect the “read operation” and “write operation” of shared resources respectively.sync.RWMutex
Lock methods and in typesUnlock
Method is used to lock and unlock the write lock respectivelyRLock
Methods andRUnlock
Methods 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 locked
goroutine
Cannot 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!