Home >Backend Development >Golang >Master the concurrency mode and parallel computing of Go language

Master the concurrency mode and parallel computing of Go language

王林
王林Original
2023-11-30 09:30:33784browse

Master the concurrency mode and parallel computing of Go language

With the rapid development of the Internet, the demand for large-scale distributed systems is getting higher and higher, and concurrent programming and parallel computing have become skills that Internet developers must master. The Go language is a language born to support concurrency. It performs very well in concurrent programming and parallel computing. This article will introduce the concurrency mode and parallel computing of Go language, and give some practical cases to help readers understand in depth.

1. Concurrency mode of Go language

The concurrency mode of Go language is mainly based on the two basic components of goroutine and channel. Goroutine is a lightweight thread, managed by the runtime system of the Go language. It can be started through the go keyword, and goroutines can communicate through channels.

The following is a simple example of goroutine and channel:

package main

import "fmt"

func printMsg(msg string, ch chan string) {
    ch <- msg
}

func main() {
    ch := make(chan string)
    msgs := []string{"Hello", "Golang", "Parallel"}

    for _, msg := range msgs {
        go printMsg(msg, ch)
    }

    for i := 0; i < len(msgs); i++ {
        fmt.Println(<-ch)
    }
}

The code starts three goroutines through a for loop and outputs three strings respectively. The printMsg function writes the string message into the channel, and the main function reads from the channel again.

1.1 Pipeline Mode

In the Go language, multiple goroutines can be connected in series through the pipeline mode to form a more complex concurrent system. The implementation of the pipeline pattern is usually through channel communication between multiple goroutines, passing data from one goroutine to another goroutine, and processing and converting the data in each goroutine. The following is a simple pipeline mode example:

package main

import (
    "fmt"
)

func addOne(in <-chan int, out chan<- int) {
    for val := range in {
        out <- val + 1
    }
    close(out)
} 

func printNums(out <-chan int) {
    for val := range out {
        fmt.Println(val)
    }
}

func main() {    
    nums := []int{1, 2, 3}
    in := make(chan int)
    out := make(chan int)

    go addOne(in, out)

    go printNums(out)

    for _, num := range nums {
        in <- num
    }
    close(in)
}

The code passes define 3 goroutines, namely input goroutine, plus 1 processing goroutine and output goroutine. The addOne function adds 1 to the data in the input channel and writes it to the output. In the channel, the printNums function reads data from the output channel and outputs it.

1.2 Selection pattern

The select statement of Go language provides a convenient way to handle multiple channels, that is, the selection pattern (select pattern). The selection mode allows us to perform non-blocking selection operations in multiple channels. When there are readable or writable messages in multiple channels, one will be automatically selected for operation.

The following is a simple selection mode example:

package main

import "fmt"

func ping(ch chan<- string) {
    for {
        ch <- "ping"
    }
}

func pong(ch chan<- string) {
    for {
        ch <- "pong"
    }
}

func printer(ch <-chan string) {
    for {
        fmt.Println(<-ch)
    }
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    ch3 := make(chan string)

    go ping(ch1)
    go pong(ch2)
    go printer(ch3)

    for {
        select {
        case msg := <-ch1:
            ch3 <- msg
        case msg := <-ch2:
            ch3 <- msg
        }
    }
}

In the code, the ping function and pong function send "ping" and "pong" messages to ch1 and ch2 respectively, and the printer function reads The message in ch3 and output. In the main function, use the select statement to monitor the messages in ch1 and ch2, and pass the received messages to the printer function through ch3 for output.

2. Parallel computing of Go language

The built-in parallel computing modules of Go language include sync, atomic and context, etc. sync and atomic mainly use mutex (Mutex) and atomic operation (atomic operation) to control concurrent data access, and context is used to manage the context information of goroutine. The following is a brief introduction to how to use these modules:

2.1 Mutex lock

Mutex lock is one of the most commonly used synchronization mechanisms to protect shared resources, and it is also the most basic synchronization mechanism in the Go language. one. In Go language, you can create a mutex lock through the Mutex type in the sync package. The Mutex type provides two important methods: Lock and Unlock. Before accessing shared resources, you need to call the Lock method to obtain the lock, and then call the Unlock method to release the lock after the access is completed. The following is a simple example of a mutex lock:

package main

import (
    "fmt"
    "sync"
)

func addOne(num *int, mutex *sync.Mutex, wg *sync.WaitGroup) {
    mutex.Lock()
    *num += 1
    mutex.Unlock()

    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    var num int

    mutex := &sync.Mutex{}
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go addOne(&num, mutex, &wg)
    }

    wg.Wait()
    fmt.Println(num)
}

In the code, the addOne function is defined to add 1 to the num variable. Before adding 1, you need to obtain the mutex lock first, and after adding 1, you need to release the mutex lock. . Use WaitGroup to wait for all goroutines to complete execution and output the final results.

2.2 Atomic operations

In high concurrency scenarios, mutex locks may reduce program performance, so the Go language provides atomic operations to replace mutex locks. The atomic package provides several atomic operation functions, such as AddInt64, CompareAndSwapInt64, SwapInt64, etc. Using atomic operations ensures that operations on variables will not be interrupted by other goroutines, and concurrent execution will not be affected. The following is a simple atomic operation example:

package main

import (
    "fmt"
    "sync/atomic"
)

func addOne(num *int64, count *int64, done chan bool) {
    for i := 0; i < 1000; i++ {
        atomic.AddInt64(num, 1)
    }
    atomic.AddInt64(count, 1)
    done <- true
}

func main() {
    var num int64
    var count int64
    done := make(chan bool)

    for i := 0; i < 100; i++ {
        go addOne(&num, &count, done)
    }

    for i := 0; i < 100; i++ {
        <-done
    }

    fmt.Printf("num=%d, count=%d
", num, count)
}

In the code, the AddInt64 function of the atomic package is used to perform atomic operations on the num variable. After the operation is completed, the main thread is notified through done. The count variable is accumulated through the ordinary AddInt64 function, and the values ​​​​of num and count are finally output.

2.3 Context Management

In the Go language, it is often necessary to pass context information between multiple goroutines, such as request ID, timeout settings, etc. The context package provides a convenient way to manage goroutine context information. When using context, you usually need to create a parent context in the main goroutine. When deriving the goroutine, use WithCancel, WithDeadline, WithValue and other functions to create a child context and pass the corresponding context information. The following is a simple context management example:

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("worker %d canceled
", id)
            return
        default:
            fmt.Printf("worker %d is working
", id)
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    for i := 0; i < 3; i++ {
        go worker(ctx, i)
    }

    time.Sleep(5 * time.Second)

    cancel()
}

In the code, use the context package to create a parent context, and create a child context through the WithCancel function. In the worker function, use the select statement to listen to the signal of ctx.Done(). When ctx.Done() is closed, it means that the context is canceled and the worker function needs to exit. In the main function, close the subcontext through the cancel function and wait for the subcontext to be canceled. The running results are as follows:

worker 0 is working
worker 1 is working
worker 2 is working
worker 2 canceled
worker 1 canceled
worker 0 canceled

When the parent context is canceled, all child contexts will receive notifications and exit execution.

3. Conclusion

This article briefly introduces the concurrency mode and parallel computing of Go language, and introduces basic components and modules such as goroutine, channel, mutex lock, atomic operation and context. By learning these basic knowledge, we can better master the concurrency and parallel programming of Go language, and lay the foundation for building high-performance, high-concurrency Internet applications.

The above is the detailed content of Master the concurrency mode and parallel computing of Go language. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn