A little digression first

I have been in UCloud lab for more than half a year, perhaps because the project is mature and stable. In addition to having a certain understanding of Golang, I have not accumulated enough background development, and I am limited to moving bricks. The advantages of Go language cannot be brought into full play, so IT is better to use Python (just a metaphor). Recently, I had the opportunity to directly take charge of a back-end module, so I can supplement some knowledge of distributed and network programming. At the same time, I will summarize and share it in my private time. The code should be as clean as possible, and the content should be as Simple as possible (Simple is better)


Directory:

  • The concept of race conditions
  • Simulate race conditions (Go code)
  • Simulating race conditions (Python code)
  • Race condition solution
  • Resolving race conditions (Go code)
  • Resolving race conditions (Python code)

TL; DR:

A race condition, also known as a race condition, or a race risk, aims to describe how the output of a system or process depends on an uncontrolled sequence or timing of events. The word comes from two signals trying to compete with each other to influence who comes out first.

For example, if two processes in a computer (threads, coroutines) attempt to modify the contents of a shared memory at the same time, without concurrency control, the final result depends on the order and timing of execution of the two processes. And if a concurrent access conflict occurs, the final result is incorrect.

Concurrency controlConcurrency Control (Concurrency Control) is a mechanism to ensure that errors caused by concurrent operations are corrected in a timely manner.


Simulate race conditions (Go code)

If you’re not interested in Go, scroll down to the Python version

Var (N = 0 waitGroup sync.waitgroup) func counter(number *int) { *number++ waitgroup.Done() } func main() { for i := 0; i < 1000; i++ { waitgroup.Add(1) go counter(&N) } waitgroup.Wait() fmt.Println(N) }Copy the code

The procedure is relatively simple, explain two points:

  1. Overall logic: concurrent 1000 goroutine, goroutine to modify the memory of N, make it +1, the program is through the way of Pointers
  2. sync.WaitGroupIs to use the locking mechanism (Concurrency controlTo ensure that the goroutine does not exit main until it returnssynchronous, it has only three methods:Add().Done().Wait(). You can probably guess how it works: Every time you start a GoroutineAdd(1), each goroutine ends onDone()In fact, it isAdd(-1)And the lastWait()Block main untilAnd zero, that is, the Goroutine is executed

Running results:

Run ten times

I ran it ten times and not only was it not 1000, it was different each time, WTF??

Yes, the reason for this result is that race conditions occur when coroutines use N objects. We can use the detection command -race provided by Go to facilitate detection:

Race testing provided by Go

As an aside: Go provides two ways to control concurrency. One is the locking mechanism used above: sync.waitgroup; The other is channel, which will definitely be introduced later.


Simulating race conditions (Python code)

# coding:utf8
import time
import threading

N = 0


def change_it(n):
    global N
    N = N + n


def run_thread(n):
    for i in range(100000):
        change_it(n)


t1 = threading.Thread(target=run_thread, args=(1,))
t2 = threading.Thread(target=run_thread, args=(1,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
Copy the code

Start two threads, both to increment N 100000 times, and the result is unstable due to race conditions:

Run ten times

Race condition solution

Use mutex

Mutual exclusion (abbreviated Mutex) introduces a state for a resource: locked/unlocked. When a thread wants to change the shared data, it is locked first. In this case, the resource status is “locked”, and other threads (processes, coroutines) cannot change the data. Other threads cannot lock the resource again until the thread releases the resource, making its state “unlocked.” The mutex ensures that only one thread writes at a time, thus ensuring data correctness in multithreaded situations.


Let’s go straight to the code:

Resolving race conditions (Go code)

Package main import (" FMT ""sync") var (N = 0 mutex sync.mutex // 1 waitGroup sync.waitgroup) func counter(number *int) { mutex.Lock() // 2 *number++ mutex.Unlock() // 3 waitgroup.Done() } func main() { for i := 0; i < 1000; i++ { waitgroup.Add(1) go counter(&N) } waitgroup.Wait() fmt.Println(N) }Copy the code

Three lines were added, annotated with numbers, resulting in the following:

Run ten times

Don’t believe it? Test race Condition:

Detect race conditions

Through ✌ ️


Resolving race conditions (Python code)

# coding:utf8
import time
import threading

N = 0
mutex = threading.Lock() # 1


def change_it(n):
    global N
    if mutex.acquire(1): # 2
        N = N + n
        mutex.release() # 3


def run_thread(n):
    for i in range(100000):
        change_it(n)


t1 = threading.Thread(target=run_thread, args=(1,))
t2 = threading.Thread(target=run_thread, args=(1,))
t1.start()
t2.start()
t1.join()
t2.join()
print(N)
Copy the code

Make three changes again, such as numerical comments, and try the result

Run ten times

Tut, tut, tut, tut


Show MOE