Let’s look at the problem with this code:

var wg sync.WaitGroup
var x int64

func main(a) {
	wg.Add(2)
	go f()
	go f()
	wg.Wait()
	fmt.Println(x) // Output: 12135
}

func f(a)  {
	for i:=0; i<10000; i++ { x = x+1
	}
	wg.Done() 
}
Copy the code

Here is why the output is 12135 (different machines have different results) instead of 20000.

Because the assignment of x is divided into three steps: fetching the value of x, calculating the result of x, and assigning x. Therefore, since goroutine coroutine is used to call f function, there is the problem of resource competition, so there is dirty data in assignment and calculation process. For such problems, mutex can be used to solve them:

The mutex

Mutex is a common way to control access to a shared resource by ensuring that only one Goroutine can access the shared resource at a time. The Mutex type of the SYNC package is used in the Go language to implement Mutex.

var wg sync.WaitGroup
var x int64
var lock sync.Mutex

func main(a) {
	wg.Add(2)
	go f()
	go f()
	wg.Wait()
	fmt.Println(x) // Output: 20000
}

func f(a)  {
	for i:=0; i<10000; i++ { lock.Lock()// add a mutex
		x = x+1
		lock.Unlock() / / unlock
	}
	wg.Done() 
}
Copy the code

Using a mutex ensures that only one goroutine enters a critical section at a time, while the rest of the goroutine waits for the lock. When the mutex is released, the waiting Goroutine can acquire the lock and enter the critical region. When multiple Goroutines are waiting for a lock at the same time, the wake-up strategy is random.

Read and write mutex

Mutex is completely mutually exclusive, but there are many practical scenarios where read more than write less. When we concurrently read a resource without modifying it, there is no need to lock it. In such scenarios, read/write locks are a better choice. Read/write locks use the RWMutex type in the SYNC package in the Go language.

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.

var (
	x1 int64
	wg1 sync.WaitGroup
	lock1 sync.Mutex
	rwlock sync.RWMutex
)

func main(a) {
	startTime := time.Now()
	for i:=0; i<100; i++ { wg1.Add(1)
		write()
	}
	for i:=0; i<10000; i++ { wg1.Add(1)
		read()
	}
	wg1.Wait()
	fmt.Println(time.Now().Sub(startTime))

	// Mutex time: 973.9304ms
	// Read and write mutex: 718.094ms
}

func read(a)  {
	defer wg1.Done()
	// lock1.lock () // mutex
	rwlock.RLock() // Read/write mutex
	fmt.Println(x1)
	// lock1.unlock () // mutex
	rwlock.RUnlock() // Read/write mutex
}

func write(a)  {
	defer wg1.Done()
	lock1.Lock()
	x1 = x1+1
	lock1.Unlock()
}
Copy the code