This article has participated in the Denver Nuggets Creators Camp 3 “More Productive writing” track, see details: Digg project | creators Camp 3 ongoing, “write” personal impact.

Parallel, concurrent

The difference between parallelism and concurrency:

  • Parallel: Execution of two or more programs at the same time.
  • Concurrency: The execution of two or more programs in the same time period.

Parallel execution of a program, at the same time, is really multiple programs running on the CPU, which requires the CPU to provide multi-core computing capabilities. Concurrent programs, on the other hand, only observe multiple programs executing on the CPU from a macro perspective, and they are executed in rapid rotation across the CPU from a micro perspective.

For processes, threads, coroutines, concurrency, parallelism, which I’ve covered in my previous article on concurrency mastery, you can take a look. Portal Go Pass 09: Concurrency Mastery, Goroutine and Channel declaration and use!

MPG thread model for Go

Go is considered a high-performance development language because of its native support for coroutine concurrency. Coroutines are user threads, lightweight threads. The scheduling of coroutines depends entirely on user-space code control.

Coroutines have their own register context and stack, and stored in user space, coroutines do not need to switch to the kernel state to access the kernel space when switching, switching speed is extremely fast. Developers need to deal with technical issues such as the preservation and recovery of context information and the management of stack space size during coroutine switching in user space.

The Go language uses a special two-level threading model, the MPG threading model:

  • M, machine, is equivalent to the mapping of the kernel thread in the Go process. It corresponds to the kernel thread one by one and represents the resource that actually performs the computation. M is associated with only one kernel thread during its lifetime.

  • P, for processor, represents the context in which the Go code fragment is executed. The combination of M and P can provide an efficient operating environment for G. The bond between them is not fixed. The maximum number of P determines the concurrency size of the Go program, as determined by the runtime.GOMAXPROCS variable.

  • G, or Goroutine, is a lightweight user thread that encapsulates the code snippet, with information about the stack, state, and code snippet at execution time.

During actual execution, multiple executable G’s are mounted sequentially below the executable G queue of P, waiting for scheduling and pointing. When M is blocked by some I/O system calls in G, P will disconnect M and obtain an M from the idle M queue of the scheduler or create a new M for combined execution, so as to ensure the execution of other G in the executable G queue in P. As the number of M parallel execution in the program does not change, So the program has high CPU utilization.

1. Select multiplexing

Select enables multiplexing, that is, listening for multiple channels at the same time.

  • If you find which channel has data generated, execute the corresponding case branch
  • If more than one case branch can be executed at the same time, one is randomly selected
  • If none of the case branches can be executed, the select waits

Example:

package main

import (
	"fmt"
)

func main(a) {
	ch := make(chan int.1)
	for i := 0; i < 10; i++ {
		select {
		case x := <-ch:
			fmt.Println(x)
		case ch <- i:
			fmt.Println("--", i)
		}
	}
}
Copy the code

Running results:

-- 0
0
-- 2
2
-- 4
4
-- 6
6
-- 8
8
Copy the code

2. Context

Context implementation can be used when Context information needs to be passed across multiple Goroutines. In addition to passing Context information, Context can also be used to pass signals that terminate the execution of a subtask, or terminate multiple goroutines that execute subtasks. Context provides the following interfaces:

type Context interface {
    // Returns the time when the Context was cancelled, the deadline for completion of the work
    Deadline() (deadline time.Time, ok bool)
    
    //1. Return a channel that closes when the current work is complete or the context is cancelled
    //2. Calling the Done method multiple times returns the same channel;
    Done() <-chan struct{}
    
    //1. Return a non-empty value only if the channel returned by Done is closed
    //2. If Context is Canceled, a Canceled error is returned
    //3. If the Context times out, a DeadlineExceeded error is returned
    Err() error
    
    // Used to get the passed key information from the Context
    Value(key interface{}) interface{}}Copy the code

In practice, a Web request may require multiple Goroutines to work together. Goroutines may need to share information about the request, and when the request is canceled or timed out, all goroutines started by the request need to be terminated, freeing resources. You need to use Context to solve these problems.

Example:

package main

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

const DB_ADDRESS  = "db_address"
const CALCULATE_VALUE  = "calculate_value"

func readDB(ctx context.Context, cost time.Duration)  {
	fmt.Println("db address is", ctx.Value(DB_ADDRESS))
	select {
	case <- time.After(cost): // simulate a database read
		fmt.Println("read data from db")
	case <-ctx.Done():
		fmt.Println(ctx.Err()) // The reason for the task cancellation

		// Some cleaning up}}func calculate(ctx context.Context, cost time.Duration)  {
	fmt.Println("calculate value is", ctx.Value(CALCULATE_VALUE))
	select {
	case <- time.After(cost): // Simulate data calculation
		fmt.Println("calculate finish")
	case <-ctx.Done():
		fmt.Println(ctx.Err()) // The reason for the task cancellation
    
		// Some cleaning up}}func main(a)  {
	ctx := context.Background(); // Create an empty context
	// Add context information
	ctx = context.WithValue(ctx, DB_ADDRESS, "localhost:10086")
	ctx = context.WithValue(ctx, CALCULATE_VALUE, 1234)

	// Execute timeout return after setting the child Context for 2s
	ctx, cancel := context.WithTimeout(ctx, time.Second * 2)

	defer cancel()

	// Set the execution time to 4 seconds
	go readDB(ctx, time.Second * 4)
	go calculate(ctx, time.Second * 4)

	// Fully implemented
    time.Sleep(time.Second * 5)}Copy the code

Running results:

calculate value is 1234
db address is localhost:10086
context deadline exceeded
context deadline exceeded
Copy the code

In the example, we simulated a database access and logical computation in a request, and closed the unfinished Goroutine when the request execution timed out.

  1. Context information is first added to the context via the context.withvalue method, which is concurrently safe in multiple Goroutines.
  2. We then use the context.WithTimeout method to set the context timeout to 2s and pass it to readDB and Calculate for the two Goroutine subtasks.
  3. In the readDB and Calculate methods, the Context’s Done channel is monitored using a SELECT statement. Since we set the child Context to time out after 2s, it will close the Done channel after 2s; However, the default execution time of the subtask is 4s, the corresponding case statement has not returned, the execution is cancelled, and the case statement of the cleanup work is entered, ending the task of the current Goroutine.