Inventory one of those things about Golang concurrency-a preliminary study

Life is short, Let's Go

Life is short, I use Python

Golang, Golang, and Golang are really waves. Today, let’s take a look at Golangconcurrency. To be precise goroutine, let’s put it aside for the time being about multi-threaded concurrency (mainly because I don’t know how to do it yet, so I dare not come out. Engage). Regarding the golangmerits, let's not talk about the false ones. Anyway, the big guys are saying that I'm just a melon-eating crowd, occasionally hitting soy sauce and fleeing~.

Speaking of concurrency, wait for a series of Sao concepts to come out, in order to be a dish to take care of yourself, review by the way

Basic concept

process

Process definition

Process (English: process) refers to what is already running on the computer 程序. The process used to be a ``time-sharing system 的基本运作单位。在面向进程设计的系统(如早期的UNIX Linux 2.4 及更早的版本)中,进程是程序的基本执行实体;在面向线程设计的系统(如当代多数操作系统、Linux` 2.6 and later versions), the process itself is not the basic running unit, but the thread container.

The program itself is only a description of instructions, data and their organization, which is equivalent to a noun. The process is the real running instance of the program (those instructions and data), which can be imagined as the present progressive form. Several processes may be related to the same program, and each process can run independently in a synchronous or asynchronous manner. Modern computer systems can load multiple programs into the memory in the form of processes within the same period of time, and use time sharing (or time division multiplexing ) to show the feeling of parallel operation on one processor at the same time . Similarly, an operating system or computer architecture that uses multi-threading technology (multi-threading means that each thread represents an independent execution context in a process). Parallel threads of the same program can run at the same time on multi-CPU hosts or networks. (On different CPUs).

Process creation

The operating system needs a way to create processes.

The following 4 main events will create a process

  1. System initialization (simple can be understood as booting after shutdown)
  2. The running program executes the system call to create the process (for example: a friend sends a URL, and you click it to open the browser to enter the web page)
  3. The user requests to create a new process (for example: open a program, open QQ, WeChat)
  4. Initialization of a batch job

Process termination

After the process is created, it starts to run and process related tasks. But it will not exist forever, and will eventually complete or withdraw. Then the process will be terminated in the following four situations

  1. Normal withdrawal (voluntary)
  2. Exit on error (voluntary)
  3. Crash exit (involuntary)
  4. Killed by others (involuntarily)

Normal exit: you exit the browser, you click on it

Exit by error: You are watching TV series with gusto, and suddenly a bug occurs in the program, which causes you to exit

Crash and exit: your program has crashed

Killed by others: For example, on windows, use the task manager to close the process

The state of the process

  1. Running state (actually occupies CPU)
  2. Ready state (can be run, but other processes are running and paused)
  3. Blocked state (unless some external time occurs, otherwise the process cannot run)

The first two states are logically similar. Processes in these two states can run, but there is no CPU allocated for the second state temporarily, and it can run once it is allocated to the CPU.

The third state is different from the first two. The process in this state cannot run, even if the CPU is idle.

If you are interested, you can learn more about the realization of the process and the multi-process design model

Process pool

The application of process pool technology consists of at least the following two parts:

Resource process

For the pre-created idle process, the management process will distribute the work to the idle process for processing.

Management process

The management process is responsible for creating resource processes, handing over work to idle resource processes for processing, and reclaiming resource processes that have finished processing work.

The concept of resource process and management process is well understood. How the management process effectively manages the resource process, assigns tasks to the resource process, recovers the idle resource process, and the management process must effectively manage the resource process, so the management process and the resource process must interact with each other , Interaction through IPC, signals, semaphores , message queues , pipes, etc.

Process pool: To be precise, it does not actually exist in our operating system, but IPC, signals, semaphores , message queues , pipes, etc. manage multiple processes, thereby reducing constant opening and closing operations. In order to reduce unnecessary resource consumption

Thread

definition

Thread (English: thread) is the smallest unit that the operating system can perform operation scheduling . In most cases, it is included in the process of being, it is the process of the actual operation of the unit. A thread refers to a process in a single sequential flow of control, a process multiple threads concurrently, each thread in parallel to perform different tasks. In Unix System V and SunOS , they are also called lightweight processes, but lightweight processes are more referred to as kernel threads, and user threads are called threads.

Thread is the basic unit of independent scheduling and dispatch. A kernel thread that can be scheduled by the operating system kernel

Multiple threads in the same process will share all system resources in the process, such as virtual address space, file descriptors , signal processing, and so on. However, multiple threads in the same process have their own 调用栈(call stack), their own 寄存器环境(register context), and their own thread-local storage.

A process can have many threads to handle, and each thread executes different tasks in parallel. If the process has a lot of tasks to complete, it requires a lot of threads and calls a lot of cores. The advantage of using multi-threaded programming on a multi - core or multi- CPU , or a CPU that supports Hyper-threading is obvious, that is, it improves the execution of the program. Throughput rate. Imagine that the core is equivalent to people at work. The more people there are, the more things can be processed at the same time. The threads are equivalent to hands. The more hands, the higher the work efficiency. On a single-CPU single-core computer, using multi-threading technology, it is also possible to separate the frequently blocked part of the process that is responsible for I/O processing and human-computer interaction from the intensive calculation part, and write a special workhorse thread to execute intensively. Although multi-tasking is not as good as multi-core computing, it has the capability of multi-threading, which improves the efficiency of program execution.

Thread Pool

Thread pool (English: thread pool): A thread usage model. Too many threads will bring scheduling overhead, which in turn affects cache locality and overall performance. The thread pool maintains multiple threads, waiting for the supervisor to assign tasks that can be executed concurrently. This avoids the cost of creating and destroying threads when processing short-term tasks. The thread pool can not only ensure the full utilization of the kernel, but also prevent over-scheduling. The number of available threads should depend on the number of available concurrent processors, processor cores, memory, network sockets, etc. For example, the number of threads is generally appropriate to take the number of CPUs + 2. Too many threads will cause additional thread switching overhead.

The common method of task scheduling to execute threads is to use synchronous queues, called task queues. The threads in the pool wait for tasks in the queue and put the completed tasks into the completion queue.

Thread pool mode is generally divided into two types: HS/HA semi-synchronous/semi-asynchronous mode, L/F leader and follower mode.

  • The semi-synchronous/semi-asynchronous mode, also known as the producer consumer mode, is a relatively common way of implementation and relatively simple. It is divided into three layers: synchronous layer, queue layer, and asynchronous layer. The main thread of the synchronization layer processes work tasks and stores them in the work queue. The work threads take out tasks from the work queue for processing. If the work queue is empty, the work threads that cannot get the tasks enter the suspended state. Because there is data communication between threads, it is not suitable for large data exchange occasions.
  • In the leader-follower mode, threads in the thread pool can be in one of three states: leader leader, follower follower or worker processor. There is only one leader thread in the thread pool at any time. When the event arrives, the leader thread is responsible for message separation, and selects one from the follower thread to be the successor leader, and then sets itself as a worker state to handle the event. After processing, the worker thread sets its own state as a follower. This mode is complicated to implement, but it avoids the exchange of task data between threads and improves the similarity of the CPU cache. In ACE (Adaptive Communication Environment), a leader-follower model implementation is provided.

The scalability of the thread pool has a greater impact on performance.

  • Creating too many threads will waste a certain amount of resources, and some threads will not be fully used.
  • Destroying too many threads will result in wasting time to create them again later.
  • Creating threads is too slow, which will lead to long waits and poor performance.
  • Destroying threads is too slow, causing resource starvation in other threads .

Coroutine

Coroutine, called Coroutine in English, is also known as microthread and fiber. Coroutine is a lightweight thread in user mode.

The coroutine has its own register context and stack. When the coroutine is scheduled to switch, save the register context and stack to other places, and restore the previously saved register context and stack when switching back. Therefore, the coroutine can retain the state of the last call, that is, a specific combination of all local states. Each time the process is reentered, it is equivalent to entering the state of the last call.

A coroutine is essentially a single process. Compared with multiple processes, a coroutine does not require the overhead of thread context switching, atomic operation locking and synchronization, and the programming model is also very simple.

Serial

Multiple tasks, execute another one after the execution is complete.

For example: take a walk after eating (sit down to eat first, go for a walk after eating)

parallel

Multiple tasks, alternate execution

For example: cooking, one will put water to wash the vegetables, and then they will absorb (the dishes are dirty, wash the dishes and write down, proud~)

Concurrent

Start together

Eating and watching TV

Blocking and non-blocking

block

The blocking state refers to the state in which the program is suspended when the required computing resources are not obtained. While the program is waiting for the completion of a certain operation, it cannot continue to process other things by itself, it is said that the program is blocked on the operation.

Common forms of blocking are: network I/O blocking, disk I/O blocking, user input blocking, and so on. Blocking is everywhere, including when the CPU switches context, all processes cannot really handle things, and they will also be blocked. If it is a multi-core CPU, the core that is performing the context switch operation cannot be used.

Non-blocking

The program is not blocked by itself while waiting for an operation, and can continue to process other things, it is said that the program is non-blocking in the operation.

Non-blocking does not exist at any program level and under any circumstances. Only when the level of program encapsulation can include independent subprogram units, can it have a non-blocking state.

The existence of non-blocking is due to the existence of blocking, and it is because of the time-consuming and inefficiency of a certain operation that is blocked, we have to make it non-blocking.

Synchronous and asynchronous

Synchronize

In order to complete a certain task, different program units need to rely on a certain communication method to coordinate during the execution process. We call these program units to be executed synchronously.

For example, in the shopping system to update the product inventory, you need to use the "row lock" as a communication signal to force different update requests to be executed in order, and then the operation of updating the inventory is synchronized.

In short, synchronization means order.

asynchronous

In order to complete a task, different program units do not need to communicate and coordinate in the process, and the task can be completed in a way that unrelated program units can be asynchronous.

For example, crawlers download web pages. After the scheduler calls the downloader, other tasks can be scheduled without maintaining communication with the downloading task to coordinate behavior. Operations such as downloading and saving of different web pages are irrelevant, and there is no need for mutual notification and coordination. The completion time of these asynchronous operations is uncertain.

Asynchronous and non-asynchronous

After the above understanding, it is process, thread, and so on. It is really uncomfortable. But I believe you already have a preliminary probability, so here we will understand 可异步and understand more deeply 不可异步.

Before that, let’s summarize, the above-mentioned various evolutionary routes, in fact, accelerating is nothing more than one sentence, improving efficiency. (Nonsense~)

Then there are two major factors that improve efficiency, increase input in order to increase output, and avoid unnecessary losses as much as possible (for example: reducing context switching, etc.).

How to distinguish whether it is asynchronous code or non-asynchronous, in fact, it is very simple, that is, whether it can autonomously complete the part that does not require our participation.

We think backward from the results,

For example, if we send a network request and there is network I/O congestion in between, then we will suspend it for testing and switch to other things. When it responds, we are proceeding to the next step in this stage. Then this is asynchronous

In addition: I am writing my homework and going to the bathroom. Suddenly, I want to go to the bathroom and go. After going to the bathroom, I came back and continued to do my homework. There will be no progress in my homework during the time I went to the bathroom, so we can understand that this is non-asynchronous

goroutine

Talk about it, talk about it, finally it's time to go to the real guy, not much nonsense.

How to achieve it only needs to define a lot of tasks, and let the system help us allocate these tasks to the CPU for concurrent execution.

Go languages goroutineis one such mechanism, goroutinethe concept is similar to threads, but goroutineby the Go runtime (runtime) scheduling and management. The Go program will intelligently allocate the tasks in the goroutine to each CPU. The Go language is called a modern programming language because it has built-in scheduling and context switching mechanisms at the language level.

In Go programming, you don’t need to write processes, threads, and coroutines yourself. There is only one skill in your skill pack- goroutinewhen you need to execute a task concurrently, you only need to wrap the task into a function , goroutineJust open one to execute this function

goroutine and thread

Growable stack

OS threads (operating system threads) generally have a fixed stack memory (usually 2MB), a goroutinestack has only a small stack at the beginning of its life cycle (typically 2KB), goroutinethe stack is not fixed, he can press Need to grow and shrink, goroutinethe stack size limit can reach 1GB, although this is rarely used. Therefore, it goroutineis possible to create about 100,000 at a time in the Go language .

goroutine model

GPMIt is the implementation of the runtime level of the Go language, and is a set of scheduling system implemented by the Go language itself. Different from the operating system scheduling OS threads.

  • GIt is easy to understand, it is a goroutine, in addition to storing the goroutine information, there is also information such as binding to the P where it is located.
  • PManages a group of goroutine queues, P will store the context of the current goroutine running (function pointer, stack address and address boundary), P will do some scheduling on the goroutine queue it manages (for example, suspend the goroutine that takes a long time in the CPU) , Run subsequent goroutines, etc.) When your own queue is consumed, it will go to the global queue to fetch it. If the global queue is also consumed, it will go to other P queues to grab tasks.
  • M(machine)It is the virtualization of Go runtime (runtime) to the kernel thread of the operating system. M and the kernel thread generally have a one-to-one mapping relationship, and a groutine will eventually be executed on M;

P and M generally correspond one-to-one. Their relationship is: P manages a group of G mounts and runs on M. When a G is blocked on an M for a long time, the runtime will create a new M, and the P where the blocked G is located will mount other Gs on the newly created M. When the old G block is completed or it is considered dead, the old M is recovered.

The number of P is runtime.GOMAXPROCSset (maximum 256). After Go1.5 version, it defaults to the number of physical threads. When the amount of concurrency is large, some P and M will be added, but not too much. If the switching is too frequent, the gain will not be worth the loss.

From the perspective of thread scheduling alone, the advantage of the Go language compared to other languages ​​is that OS threads are scheduled by the OS kernel, goroutinewhich is scheduled by the Go runtime's own scheduler. This scheduler uses a scheduler called m :n scheduling technology (multiplexing/scheduling m goroutines to n OS threads). One of its major features is that goroutine scheduling is done in user mode, and does not involve frequent switching between kernel mode and user mode, including memory allocation and release. It maintains a large memory pool in user mode. Calling the malloc function of the system directly (unless the memory pool needs to be changed) is much cheaper than scheduling OS threads. On the other hand, the multi-core hardware resources are fully utilized, and several goroutines are approximately equally divided among the physical threads. Coupled with the ultra-light weight of the goroutine itself, the above all ensure the performance of go scheduling.

GOMAXPROCS

The scheduler of the Go runtime uses GOMAXPROCSparameters to determine how many OS threads need to be used to execute Go code at the same time. The default value is the number of CPU cores on the machine. For example, on an 8-core machine, the scheduler will dispatch Go code to 8 OS threads at the same time (GOMAXPROCS is n in m:n scheduling).

In the Go language, the runtime.GOMAXPROCS()number of CPU logic cores occupied by the current program can be set through functions.

Before Go1.5, single-core execution was used by default. After Go1.5 version, all CPU logic cores are used by default.

The creation of goroutine

It goroutineis very simple to use , just add a gokeyword in front of the function name when calling a function , and you can create one for a function goroutine.

One goroutinemust correspond to one function, of course, you can also create multiple goroutineto execute the same function.

The syntax is as follows

func main() {
    
    
	go 函数()[普通函数和匿名函数即可]
}

If you are enthusiastic and want to try it right now, I just want to say to you, "Young man, please wait~", I haven't finished speaking yet. Above, I only talked about how to create it goroutine, but didn't say that this is how it is used. Hehe~

First, let's take a look at the unused goroutinecode, the example is as follows

# example
package main

import (
	"fmt"
	"time"
)

func example(i int) {
    
    
	//fmt.Println("HelloWord~, stamp is", i)
	time.Sleep(time.Second)
}

// normal
func main() {
    
    
	startTime := time.Now()
	for i := 0; i < 10; i++ {
    
    
		example(i)
	}
	fmt.Println("Main~")
	spendTime := time.Since(startTime)
	fmt.Println("Spend Time:", spendTime)
}

The input results are as follows

Then let's use goroutineand run

The sample code is as follows:

package main

import (
	"fmt"
	"time"
)

func example(i int) {
	fmt.Println("HelloWord~, stamp is", i)
	time.Sleep(time.Second)
}

// normal
func main() {
	startTime := time.Now()
	// 创建十个goroutine
	for i := 0; i < 10; i++ {
		go example(i)
	}
	fmt.Println("Main~")
	spendTime := time.Since(startTime)
	fmt.Println("Spend Time:", spendTime)
}

The output is as follows

At first glance, the speed increase of the good guy is not an order of magnitude, seconds~

If you look closely, you will find that where does 7,9 go? Gone, stare~

The answer will be revealed in the next article~

Looking forward to the next article, the second of those things about Golang concurrency, goroutineconcurrency control is handy

Guess you like

Origin blog.csdn.net/wzp7081/article/details/113666699