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