SoFunction
Updated on 2025-03-05

In-depth analysis of the use of goroutine and channel in Golang

-See a requirement

Requirements: Which of the numbers that require 1-900000000 are prime numbers?

analyze:

  • The traditional method is to use a loop to determine whether each number is a prime number.
  • Use concurrency or parallelism to assign the task of counting prime numbers to multiple goroutines to complete, and then goroutine will be used.

2. Introduction to processes and threads

  • The process is a process of execution of a program in the operating system, and is the basic unit of resource allocation and scheduling of the system.
  • A thread is an execution instance of a process and is the smallest unit of program execution. It is a smaller basic unit that can run independently than a process.
  • A process can create and destroy multiple threads, and multiple threads in the same process can be executed concurrently.
  • A program has at least one process, and a process has at least one thread

3. Concurrency and parallelism

  • Multithreaded programs run on a single core, which is concurrency
  • Multiple programs run on multiple cores, that is, parallel

Concurrency: Because it is on a CPU, for example, there are 10 threads, each thread performs 10 milliseconds (polling operations), from a human point of view, it seems that these 10 threads are running, but from a microscopic perspective, at a certain point in time, there is actually only one thread executing, which is concurrency.

Parallelism: Because it is on multiple CPUs (for example, there are 10 CPUs), for example, there are 10 threads, each thread executes for 10 milliseconds (each executes on different CPUs), from a human point of view, these 10 threads are running, but from a microscopic perspective, at a certain point in time, there are 10 threads executing at the same time, which is parallelism

Coroutines and Go main thread

Go main thread (some programmers directly call it thread/can also be understood as a process): One Go thread can produce multiple Ctrip. You can understand it in this way. Ctrip is a lightweight thread.

Features of Go coroutine

There is independent stack space

Shared program heap space

Scheduling is controlled by the user

Ctrip is a lightweight thread

Case description

Please write a program to complete the following functions:

1. In the main thread (which can be understood as a process), open a goroutine, and the Ctrip outputs "hello, world" every 1 second

2. In the main thread, it also outputs "hello, golang" every second. After outputting 10 times, exit the program.

3. Require the main thread and goroutine to execute at the same time

4. Draw the main thread and coroutine execution flowchart

Code implementation

// In the main thread (which can be understood as a process), open a goroutine, and the coroutine outputs "hello, world" every second// In the main thread, it also outputs "hello,golang" every second. After 10 outputs, exit the program// Requires the main thread and goroutine to execute simultaneously//Write a function and output "hello, world" every 1 secondfunc test(){
   for i := 1;i<=10;i++{
		("test() hello,world"+(i))
		()
	}
}
func main(){
    go test() // A coroutine is enabled    for i:=1;i<=10;i++{
		(" main() hello,golang"+(i))
		()
	}
}

Summarize

  • The main thread is a physical thread that acts directly on the CPU, is heavyweight and consumes very much CPU resources.
  • The coroutine opened from the main thread is a lightweight thread and a logical state. Relatively less resource consumption.
  • Golang's coroutine mechanism is an important feature, and it can easily open tens of thousands of coroutines. The concurrency mechanisms of other programming languages ​​are generally based on threads. They open too many threads and consume a lot of resources. This highlights Golang's advantages in concurrency.

Basic introduction to MPG mode

M: The main thread of the operating system (it is a physical thread)

P: The context required for coroutine execution

G: Coroutine

5. Set the number of CPUs that Golang runs

Introduction: In order to make full use of the advantages of multi-CPU, set the number of running CPUs in the Golang program

 package main
 import "fmt"
 import "runtime"
func main(){
	// Get the current system CPU number	num := ()
	// Here is the CPU of num-1 to run the go program	(num)
	("num=",num)
}
  • After go1.8, the program runs on multiple cores by default, so you don't need to set it up.
  • Before go1.8, you still need to set it up to make the CPU more efficient

(Pipe) Depend on demand

Requirements: Now we need to calculate the factorials of each number of 1-200, and put the factorials of each number into the map. Finally displayed. Required to be done using goroutine

Analysis ideas:

Use goroutine to complete it, which is efficient, but there will be concurrency/parallel security issues.

Here we ask the question of how different goroutines communicate

Code implementation

Use goroutine to complete (see what problems will occur when using gorotine concurrent completion? Then we will solve it)

When running a program, how to know whether there is a resource competition problem? The method is very simple. When compiling the program, add a parameter -race

How to communicate between different goroutines

1. Mutex locks of global variables

2. Use pipeline channel to solve it

Using global variable locking synchronization improvement program

  • English does not lock the global variable m, so there will be resource competition problems, and the code will have errors, prompting concurrent map writes
  • Solution: Add a mutex lock
  • The factorial of our number is very large, and the result will be cross-border. You can change the factorial to sum += uint64(i)

Source code

package main
import (
	"fmt"
	"time"
	"sync"
)
// Requirements: Now we need to calculate the factorials of each number from 1 to 200, and put the factorials of each number into the map// Finally displayed.  Required to be done using goroutine// Ideas// 1. Write a function to calculate the factorials of each number and put it into the map// 2. We started multiple coroutines, and the statistics are put into the map// 3. The map should make a globalvar (
  myMap = make(map[int]int,10)
  // Declare a global mutex lock  // lock is a global mutex lock  //sync is package: synchornized  // Mutex: It's a mutually exclusive  lock 
)
// The test function is to calculate n!  , let this result be put into myMapfunc test(n int){
	res := 1
	for i := 1;i<=n;i++{
		res *= i
	}
	// Here we put res into myMap	// Add lock	()
	myMap[n] = res  // concurrent map writes?
	// Unlock	()
}
func main(){
	// We open multiple coroutines here to complete this task [200]	for i := 1;i<=20;i++{
		go test(i)
	}
	// Sleep for 10 seconds [Second question]	( * 10)
	()
	// Here we output the result of variable	for i,v := range myMap{
		("map[%d]=%d\n",i,v)
	} 
	()
}

channel (pipe) - basic use

Channel initialization

Note: Use make for initialization

var intChan chan int

intChan = make(chan int,10)

Write (storage) data into the channel

var intChan chan int

intChan = make(chan int,10)

num := 999

intChan <-10

intChan <-num

Initialization of the pipeline, writing data to the pipeline, reading data from the pipeline and basic precautions

package main
import (
	"fmt"
)
func main(){
	// Demonstrate the use of pipelines	// 1. Create a pipeline that can store 3 int types	var intChan chan int
	intChan = make(chan int,3)
	// 2. See what intChannel is	("intChan Value of=%v intChanThe address of itself=%p\n",intChan,&amp;intChan)
	// 3. Write data to the pipeline	intChan&lt;- 10
	num := 211
	intChan&lt;- num
	// Note that when we write data to the tube, it cannot exceed its capacity	intChan&lt;- 50
	// intChan&lt;- 98
	//4. Check the length and cap (capacity) of the pipeline	("channel len=%v cap=%v \n",len(intChan),cap(intChan)) // 2,3
	// 5. Read data from the pipeline	var num2 int 
	num2 = &lt;-intChan
	("num2=",num2)
	("channel len=%v cap=%v \n",len(intChan),cap(intChan)) // 2,3
	// 6. If our pipeline data has been retrieved, if we fetched it again, we will report deadlock	num3 := &lt;-intChan
	num4 := &lt;-intChan
	// num5 := &lt;-intChan
	("num3=",num3,"num4=",num4)//,"num5=",num5)
}

Notes on using channel

Only the specified data type can be stored in it

After the data is full, it can no longer be put in

3. If you take out the data from the channel, you can continue to put it in.

4. If the channel data is fetched without using a coroutine, it will report dead lock

Sample code

package main
import (
	"fmt"
)
type Cat struct{
	Name string
	Age int
}
func main(){
	// Define a pipeline that stores any data type 3 data	// var callChan chan interface{}
	allChan := make(chan interface{},3)
	allChan&lt;- 10
	allChan&lt;- "tom jack"
	cat := Cat{"Little Cat",4}
	allChan&lt;- cat
	// We want to get the third element in the pipeline, and then first 2 are pushed out first	&lt;-allChan
	&lt;-allChan
	newCat := &lt;-allChan // What is the Cat taken from the pipe?	("newCat=%T,newCat=%v\n",newCat,newCat)
	// The following is wrong!  Compilation fails	// ("=%v",)
	// Use type assertion	a := newCat.(Cat)
	("=%v",)
}

Channel's closure

Use the built-in function close to close to the channel. When the channel is closed, you can no longer write data to the channel, but you can still read data from the channel

Channel traversal

Channel supports for-range mode for traversal, please pay attention to two details

  • During traversal, if the channel is not closed, an error of deadlock will appear
  • During traversal, if the channel has been closed, the data will be traversed normally. After traversal, the traversal will be exited.

Code demo:

package main
import (
	"fmt"
)
func main(){
	intChan := make(chan int,3)
	intChan&lt;- 100
	intChan&lt;- 200
	close(intChan) // close
	// This cannot be written to the channel	// intChan&lt;-300
	("okook~")
	// When the pipeline is closed, it is OK to read the data	n1 := &lt;-intChan
	("n1=",n1)
	// Traverse the pipeline	intChan2 := make(chan int,100)
	for i := 0; i&lt; 100;i++{
		intChan2&lt;-i*2 // Put 100 data into the pipeline	}
	// You cannot use ordinary for loops when traversing the pipeline	// During traversal, if the channel is not closed, the deadlock error will appear	// During traversal, if the channel has been closed, the data will be traversed normally. After traversal, the traversal will be exited.	close(intChan2)
	for  v := range intChan2{
		("v=",v)
	}
}

This is the article about the in-depth analysis of the use of goroutine and channel in Golang. For more related Go goroutine and channel content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!