Go: Concurrency

Basics

  1. What is a thread?
    A thread is a path of execution within a process.
  2. Why multi-threading?
    The idea is to achieve parallelism by dividing a process into multiple threads. For example, in a browser, multiple tabs can be different threads.
  3. Process and thread
    The primary difference is that threads within the same process run in a shared memory space, while processes run in separate memory spaces.

Goroutine

A goroutine is a lightweight thread managed by the Go runtime.

go f(x, y, z) // starts a new goroutine by running f(x, y, z)

The evaluation of f, x, y, z happens in the current goroutine and the execution of f happens in the new goroutine.

Goroutines run in the same address space, so access to shared memory must be synchronized. The sync package provides useful primitives, although you won’t need them much in Go as there are other primitives.

  1. Case 1
func display(str string) {
    
     
	time.Sleep(2 * time.Second) 
	fmt.Println(str) 
}
func main() {
    
     
	display("NORMAL")
	go display("GOROUTINE")
}

Output: NORMAL
2. Case 2

func display(str string) {
    
     
	time.Sleep(2 * time.Second) 
	fmt.Println(str) 
}
func main() {
    
     
	go display("GOROUTINE")
	display("NORMAL")
}

Output: GOROUTINE\nNORMAL
3. Case 3

func display(str string) {
    
    
	fmt.Println(str) 
}
func main() {
    
     
	go display("GOROUTINE")
	display("NORMAL")
}

Output: NORMAL

Channels

Channels are a typed conduit through which you can send and receive with the channel operator, <-.

ch <- v // send v to channel ch
v := <- ch // receive from ch, and assign value to v

// like map and slice, channel must be created before use
ch := make(chan int)

Example of two goroutines solving one task together:

func sum(s []int, c chan int) {
    
    
	sum := 0
	for _, v := range s {
    
    
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
    
    
	s := []int{
    
    7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

Buffered channels

Channels can be buffered.
Provide the buffer length as the second argument to make to initialize a buffered channel:

ch := make(chan int, 100)

Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

func main() {
    
    
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	ch <- 3 // deadlock, all goroutines are asleep
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

Range and close

A sender can close a channel:

ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)

Receivers can test if a channel is closed:

v, ok := <-ch

The loop for i := range ch receives values from channel ch repeatedly until it is closed.
Sending on a closed channel will cause a panic.
Channels aren’t like files; you don’t usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

func fibonacci(n int, c chan int) {
    
    
	x, y := 0, 1
	for i := 0; i < n; i++ {
    
    
		c <- x
		x, y = y, x+y
	}
	close(c)
}
func main() {
    
    
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
    
    
		fmt.Println(i)
	}
}

Select

The select statement lets a goroutine wait on multiple communication operations (such as c <- x).
A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

func fibonacci(c, quit chan int) {
    
    
	x, y := 0, 1
	for {
    
    
		select {
    
    
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
    
    
	c := make(chan int)
	quit := make(chan int)
	go func() {
    
    
		for i := 0; i < 10; i++ {
    
    
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

Syntax for anonymous goroutine function:

go func() {
    
    
	// function body
}()

The default case in a select is run if no other case is ready.

sync.Mutex

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
    
    
	mu sync.Mutex // make sure only one goroutine can access a variable at a time to avoid conflicts
	v  map[string]int
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
    
    
	// define a block of code to be executed in mutual exclusion by surrounding it with a call to Lock and Unlock
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mu.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
    
    
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mu.Unlock() // use defer to ensure the mutex will be unlocked
	return c.v[key]
}

func main() {
    
    
	c := SafeCounter{
    
    v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
    
    
		go c.Inc("somekey")
	}
	
	fmt.Println(c.Value("somekey"))
}

猜你喜欢

转载自blog.csdn.net/Hita999/article/details/112614638