Speaking of mutex, let’s start by abstracting a scenario

Now there is a toilet, only a pit, more people came to the toilet, want to easy and convenient, but there is only one hole So, can in turn into, after go in at the same time, in order to prevent the people behind while you didn’t finish convenient and come in a convenient, will the toilet door, after done, will open the lock, come out to make room, let the second person in convenient, After the second in the front of the same locking, unlocking action above the behavior, in the program understanding is called mutex

Now consider the explanation of mutex

In concurrent programs, there are critical resource security issues. If multiple coroutines access a shared data resource, the shared resource is not secure. Channel is used to solve the costep-in problem, but the Go language also provides traditional synchronization tools.

What is a lock? When a coroutine (thread) accesses a resource, it locks it first to prevent other coroutines from accessing it. After the access is completed, other coroutines will lock it to access it. Typically used to deal with critical resource problems in concurrency.

The sync package in the Go language package provides two lock types: sync.mutex and sync.rwmutex.

Mutex is the simplest type of lock. Mutex is a Mutex, but it is also a violent lock. Once a Goroutine acquires a Mutex, the other Goroutines have to wait until the goroutine releases the Mutex.

Each resource corresponds to a marker, called a mutex, that ensures that only one coroutine (thread) can access the resource at any one time. The other coroutines have to wait.

Mutex, represented by the Mutex structure type in sync, is the primary means of access control for shared resources in traditional concurrent programming. The sync.Mutex type has only two open pointer methods, Lock and Unlock. Lock Locks a shared resource. Unlock unlocks it.

When using a mutex, note the following: After performing operations on resources, unlock them. Otherwise, process execution exceptions and deadlocks may occur. Usually defer. Once locked, use the defer statement to ensure that the mutex is unlocked in a timely manner.

Official advice

Channels are recommended to handle synchronization, but traditional synchronization tools are also available, so the selection depends on the business

Look at the code
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// The number of pits in wc defaults to zero, i.e. = 0
var pit int

// Define a lock object
var mutex sync.Mutex

func main(a) {
	// Here simulate 5 people to go to WC
	go wc("甲")
	go wc("乙")
	go wc("丙")
	go wc("Ding")
	go wc("Frame")
	// The main goroutine gives time slices for child Goroutine to preempt execution
	time.Sleep(3 * time.Second)
	fmt.Println("Clean...")}func wc(human string) {
	// Add random seeds
	rand.Seed(time.Now().UnixNano())

	// When the program starts running, the first random child of the goroutine will be locked
	// The first person to go to wc
	fmt.Println(human, "Try to open the door")
	if pit == 0 {
		fmt.Println(human, "Get in the door and get to work.")
		// The pit is occupied
		pit = 1
		fmt.Println(human, "In business...")
		time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
		fmt.Println(human, "Get out the door.")
		// Empty pit waiting for..
		pit = 0
	} else {
		fmt.Println(human, "Spotted someone.")}}Copy the code
The result of not locking
# The above logic does not produce a pit occupied by more than one person, but a careful observation will find that only one or two random people successfully execute the "work", other people try to open the door failed, did not execute the "work", and finally the program endsB try to open the door b enter the door, start work MAO try to open the door MAO found someone b work in... A try to open the door a discovery somebody c try to open the door C discovery somebody b finish go out ding try to open the door ding take the door, begin to handle affairs ding handle affairs... Ding finished going out to clean...Copy the code
Mutex processing
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// The number of pits in wc defaults to zero, i.e. = 0
var pit int

// Define a lock object
var mutex sync.Mutex

func main(a) {
	// Here simulate 5 people to go to WC
	go wc("甲")
	go wc("乙")
	go wc("丙")
	go wc("Ding")
	go wc("Frame")
	// The main goroutine gives time slices for child Goroutine to preempt execution
	time.Sleep(3 * time.Second)
	fmt.Println("Clean...")}func wc(human string) {
	// Add random seeds
	rand.Seed(time.Now().UnixNano())

	// When the program starts running, the first random child of the goroutine will be locked
	// The first person to go to wc
	mutex.Lock()
	fmt.Println(human, "Try to open the door")
	if pit == 0 {
		fmt.Println(human, "Get in and lock it.")
		// The pit is occupied
		pit = 1
		fmt.Println(human, "In business...")
		time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
		fmt.Println(human, "Get out the door.")
		// Empty pit waiting for..
		pit = 0
	} else {
		fmt.Println(human, "Spotted someone.")
	}
	mutex.Unlock()
}
Copy the code
Lock the results
A tried to open a door a door, lock a handle... A finished out of the door to try to open the door, the lock of the office... Go out try to open the door, lock the door... Ding finish out c try to open the door c enter the door, lock c service... C finish go out b try to open the door b take the door, lock up b in handling affairs... B went out to clean...Copy the code
Look at the frontCritical resource security issuesExample of an oversold problem using a mutex
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// Quantity of goods
var commodity int = 10
var m sync.Mutex

func main(a) {
	// To speed up the program's response, we start four goroutines to handle the sell action
	go buy("1.")
	go buy("2.")
	go buy("3.")
	go buy("4.")
	time.Sleep(3 * time.Second)
}

func buy(user string) {
	// Add random seeds
	rand.Seed(time.Now().UnixNano())
	for {
		m.Lock()
		// When the quantity of goods is sufficient
		if commodity > 0 {
			After all, the real scenario is affected by server resources, IO processing and network factors
			// The processing time is not exactly the same
			time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
			commodity--
			fmt.Println(user, "Window sold 1, remaining:", commodity)
		} else {
      // Unlock if conditions are not met
      mutex.Unlock()
			fmt.Println("Oho, sold out, sold out...")
			// End the loop
			break
		}
		m.Unlock()
	}
}
Copy the code
Purchase example execution results
④ window sold away 1, remaining: 9 ④ window sold away 1, remaining: 8 ④ window sold away 1, remaining: 7 ④ window sold away 1, remaining: 6 ④ window sold away 1, remaining: 5 ④ window sold away 1, remaining: 4 ④ window sold away 1, remaining: 3 ④ window sold away 1, remaining: 2 ④ window sold out 1, surplus: 1 ④ window sold out 1, surplus: 0 oh ho, sold out...Copy the code
Combined with synchronous waiting group, sleep of main goroutine was removed
package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// Quantity of goods
var commodity int = 10
var m sync.Mutex
// Synchronize the wait group
var w sync.WaitGroup

func main(a) {
	// To speed up the program's response, we start four goroutines to handle the sell action
  
  // Set four waiting groups
	w.Add(4)
	go buy("1.")
	go buy("2.")
	go buy("3.")
	go buy("4.")
  
  // enter a block
	w.Wait()
}

func buy(user string) {
	// Add random seeds
	rand.Seed(time.Now().UnixNano())
  // Wait group -1
	defer w.Done()
	for {
		m.Lock()
		// When the quantity of goods is sufficient
		if commodity > 0 {
			After all, the real scenario is affected by server resources, IO processing and network factors
			// The processing time is not exactly the same
			time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
			commodity--
			fmt.Println(user, "Window sold 1, remaining:", commodity)
		} else {
			m.Unlock()
			fmt.Println("Oho, sold out, sold out...")
			// End the loop
			break
		}
		m.Unlock()
	}
}
Copy the code