This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021
What is concurrency security?
In high concurrency scenarios, processes and threads (coroutines) may compete for resources, resulting in dirty data reads, dirty data writes, and deadlocks. To avoid such problems, concurrency security is developed.
Here’s a simple example:
1 var data int
2 go func() {
3 data++
4 }()
5 if data == 0 {
6 fmt.Printf("the value is %v.\n", data)
7 }
Copy the code
In line 2 of this code, the go keyword opens a new coroutine to perform the data++ operation. In line 5, the data variable is read and judged. The above two threads/coroutines are run by two different threads/coroutines, and there is no guarantee of execution order, so the execution result is uncertain.
- No output. (Line 3 is executed before line 5)
- The value is 0. (Lines 5 and 6 are executed before line 3)
- The value is 1. (Line 5 is executed before line 3, but line 3 before line 6)
How does Go ensure concurrency security
As far as we know, there are about three kinds, Mutex, Channel and Atomic
Mutex
Locking is probably the most common concurrency control method, generally divided into two types, optimistic locking and pessimistic locking.
Locks are implemented by the operating system’s scheduler, usually to protect a piece of logic,
Pessimistic locking
Pessimism lock is a type of pessimistic thinking that the worst is likely to happen. Lock the resource before operating on it, regardless of whether an unexpected result will occur. For example, mutex and read-write locks are pessimistic locks.
In Go, all but Automic are pessimistic locks
Pessimistic locking should be implemented by the operating system scheduler, usually used to protect a section of logic, mainly by blocking other threads, to ensure that only one thread is currently operating on resources, so the performance is relatively poor, wasting the advantages of multi-core computer.
Optimistic locking
The idea of optimistic locking is the opposite of the idea of pessimistic locking. It always believes that resources and data will not be modified by others, so the read will not be locked, but optimistic locking will determine whether the current data has been modified during the write operation. The implementation scheme of optimistic lock mainly includes CAS and version number mechanism. Optimistic locking is suitable for multi-read scenarios and improves throughput.
Version number mechanism
By adding a version number field to the table, the version number value changes when the data is updated. For example, if thread A wants to update the value of variable S, it reads the version number at the same time as the value of S. When submitting the update, it compares the previously read version number with the current version number. The update will be triggered only when and only when the version number is the same.
CAS
CAS, which stands for Compare And Swap, is a well-known lock-free algorithm. In the case of not using locks, to achieve variable synchronization between multiple threads, that is, in the case of no thread is blocked to achieve variable synchronization, so also called non-blocking synchronization,
The mutex
GO uses the Mutex type of the Sync package to implement Mutex, which ensures that only one Goroutine can access a resource at a time.
func sample() {
var l sync.Mutex
l.Lock()
defer l.Unlock()
// so something
}
Copy the code
Read and write mutex
GO uses the RWMutex type of the Sync package to implement mutex. When we read a resource concurrently, as long as no data is written, there is no need to lock it. Therefore, using read/write mutex is a better choice for better performance when there are more reads and fewer writes.
There are two types of read-write locks: read locks and write locks. When one goroutine acquires a read lock, the other Goroutine acquires a read lock and waits for a write lock. When one Goroutine acquires a write lock, the other Goroutines wait to acquire either the read lock or the write lock.
package main import ( "fmt" "sync" "time" ) var ( wg sync.WaitGroup rwlock sync.RWMutex ) func write() { rwlock.Lock() Sleep(10 * time.millisecond) rwlock.Unlock() wg.done ()} func read() {rwlock.RLock() // rwlock. Sleep(10 * time.millisecond) rwlock.Unlock() {wg.done ()} func read() {rwlock.RLock() // rwlock.RLock( Time.sleep (time.millisecond) rwlock.RUnlock() {wg.done ()} func main() {start := time.now () // read more for I := 0; i < 1000; I ++ {wg.add (1) go read()} i < 10; i++ { wg.Add(1) go write() } wg.Wait() end := time.Now() fmt.Println(end.Sub(start)) }Copy the code