10 goroutine和channel

go concurrent programming concept

Almost all languages ​​support concurrent programming. Go is no exception, and lightweight concurrent programming (coroutine) is an important feature of Golang.
basic concept

Process: A process is an execution process of a program in the operating system. It is the basic unit of the system for resource allocation and scheduling.
Thread: It is an execution instance of a process, the smallest execution unit of a program, and a basic unit that is smaller than a process and can run independently. A process can create and destroy multiple threads. A program has at least one process, and a process contains at least one thread.
Concurrency: Multi-threaded programs run on a single core, which is concurrency. Only one thread is executing at a time, and multiple threads take turns. Execution (the time for each thread to execute in turn is very short, macroscopically it seems that multiple threads execute at the same time).
Parallel: A multi-threaded program is also running on multiple cores in parallel, and multiple threads are executing at a certain time.

Go threads and coroutines: multiple coroutines (lightweight threads unique to go) can be launched on one GO thread.
Features of coroutines: independent stack space, shared program heap space, scheduling controlled by the user, the
main thread is a physical Thread, directly acting on the CPU, is heavyweight and consumes CPU resources very much. A coroutine is a lightweight thread started from the main thread. The general language does not support the opening of more coroutine threads, which will consume a lot of resources, but golang can easily open tens of thousands of coroutines.
Golang can set the number of CPUs that the program runs (multi-core operation by default after 1.8)

num := runtime.NumCPU()
	runtime.GOMAXPROCS(num)
	fmt.Println("程序运行cpu数量:",num)

goroutine

Sample code
Start a coroutine to output one sentence per second, and the main thread also outputs one sentence per second

func test01(){
    
    
	go sayHello()//启动协程执行逻辑

	for i:=0;i<10;i++{
    
    
		fmt.Println("主线程 ====》",i)
		time.Sleep(time.Second)
	}
}

//定义一个协程方法
//每秒输出一个 hello world 输出 10次
func sayHello(){
    
    
	for i:=0;i<10;i++{
    
    
		fmt.Println("协程 hello world!",i)
		time.Sleep(time.Second)
	}
}

Execution result, you can see that the two logics are being executed at the same time

主线程 ====0
协程 hello world! 0
协程 hello world! 1
主线程 ====1
主线程 ====2
协程 hello world! 2
协程 hello world! 3
主线程 ====3
协程 hello world! 4
主线程 ====4
主线程 ====5
协程 hello world! 5
协程 hello world! 6
主线程 ====6
主线程 ====7
协程 hello world! 7
协程 hello world! 8
主线程 ====8
主线程 ====9
协程 hello world! 9

Calculate the order addition of integers between 1-100 and output the result to a map

//示例 多协程计算 0-200 每个数字的阶乘,把结果放到map 中
// 执行完成后主线程打印结果
//声明全局变量
//不加锁时 没有出现任何异常
var(
	resMap = make(map[int]int,10)
)
func test02(){
    
    
	for i:=1;i<=100;i++{
    
    
		go nmuls(i)
	}
	time.Sleep(time.Second*10)
	fmt.Println(resMap)//打印map
	//map[85:3656 48:1177 78:3082 35:631 41:862 55:1541 63:2017 66:221...
}
//阶加计算方法
func nmuls(num int)int{
    
    
	res:=1
	for i:=1;i<=num;i++{
    
    
		res += i
	}
	resMap[num]=res //将结果放入全局map中
	return res	
}

channel

How to communicate between different coroutines
can use a global mutex or use a pipe (the pipe itself is thread-safe)

Declare
var variable name chan data type, such as: var intChan chan int Declare a channel that only puts int data

important point

  1. The essence of channel is a data structure-queue, first in first out.
  2. The channel itself is thread-safe
  3. Channels are typed and can only store data of the same type as the declared type.
  4. The channel is a reference type and must be initialized (make) before writing data
  5. After the elements in the channel are filled, the dead lock exception will be reported when the elements are put
  6. Without using the coroutine, if the data in the chnnel has been fetched, deadlock will be reported after fetching
  7. If the channel is not closed during traversal, a deadlock exception will be reported. The reason should be the same as 6.
  8. The channel can be declared as read-only/write-only. See example for details

Sample code

func test03(){
    
    
	var intChan chan int
	intChan = make(chan int,3)  //创建一个可以存放 3个int的channel
	fmt.Printf("intChan 值 =%v 地址是=%p \n",intChan,&intChan)
	//intChan 值 =0xc042076080 地址是=0xc04206e018

	//插入数据
	intChan<- 10
	intChan<- 2
	intChan<- 13
	//fatal error: all goroutines are asleep - deadlock! 
	//添加的元素超过 容量时 鲍deadlock 错误
	//intChan<- 4
	//intChan 长度 len= 3 容量= 3
	fmt.Printf("intChan 长度 len= %v 容量= %v \n",len(intChan),cap(intChan))

	//读取数据
	num2:=<-intChan
	fmt.Println("num2=",num2)
	fmt.Printf("intChan 长度 len= %v 容量= %v \n",len(intChan),cap(intChan))
	num3:=<-intChan
	num4:=<-intChan
	//没有元素的时候获取元素 会报异常 fatal error: all goroutines are asleep - deadlock!
	//num4=<-intChan
	fmt.Println(num3,num4)


	//创建可以放所有类型的 channel
	var aChan chan interface{
    
    }
	aChan = make(chan interface{
    
    },5)
	aChan<- 1
	aChan<- "123"
	aChan<- 12.12
	 o:=<-aChan
	fmt.Println("取出元素:",o)//取出元素: 1


	//channel 遍历 不能通过普通的for 循环遍历 (数量可能会变化)
	//
	// for i:=0;i<len(aChan);i++{
    
    

	// }
	//遍历时如果 channel 没有关闭 则会报 deadlock 异常,fatal error: all goroutines are asleep - deadlock!
	//如果channel 关闭了,则不会报异常,遍历完 程序退出
	fmt.Printf("aChan 长度 len= %v 容量= %v \n",len(aChan),cap(aChan))

	close(aChan)
	for v:=range aChan{
    
    
		fmt.Println("v=",v)
	}
}

Mutex lock to achieve data synchronization example

//示例 多协程计算 0-200 每个数字的阶乘,把结果放到map 中
// 执行完成后主线程打印结果
//声明全局变量
//不加锁时 没有出现任何异常
var(
	resMap = make(map[int]int,10)
	lock sync.Mutex //定义全局排他锁
)
func test02(){
    
    
	for i:=1;i<=20;i++{
    
    
		go nmuls(i)
	}
	time.Sleep(time.Second*10)
	fmt.Println(resMap)//打印map
	//map[85:3656 48:1177 78:3082 35:631 41:862 55:1541 63:2017 66:221
}
//阶加计算方法
func nmuls(num int)int{
    
    
	res:=1
	for i:=1;i<=num;i++{
    
    
		res += i
	}
	lock.Lock()//写数据前枷锁
	resMap[num]=res //将结果放入全局map中
	lock.Unlock()//写数据完成进行解锁
	return res	
}

Channel example
Count the numbers from 1-20000, which are prime numbers

func test04(){
    
    
	intChan := make(chan int,1000)
	primeChan := make(chan int,2000)
	exitChan := make(chan bool,4)
	//开启协程 插入数据
	go putNum(intChan,100)

	//开启四个 计算协程
	for i:=1;i<=4;i++{
    
    
		go primeNum(intChan,primeChan,exitChan)
	}

	//当 exitchannel中取出四个元素后 证明任务执行完毕
	go func(){
    
    
		for i:=1;i<=4;i++{
    
    
			<-exitChan
		}
		close(primeChan)
	}()
	//遍历 primeChan
	fmt.Println("素数共",len(primeChan),"个如下:")
	for v:=range primeChan{
    
    
		fmt.Println(v)
	}

}
//放指定数量的数到channel中
func putNum(intChan chan int,num int){
    
    
	for i:=1;i<=num ;i++{
    
    
		intChan<-i
	}
	//添加完了 关闭 channel
	close(intChan)
}
//从 channel中取数据并判断是否为素数,如果是 则添加到 素数 channel中
//如果取不到数据了(数据一处理完)则 exitChannel中添加元素并 退出程序
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){
    
    
	var flag bool
	for{
    
    
		time.Sleep(time.Millisecond*10)//睡10毫秒
		num,ok:=<-intChan
		if !ok{
    
    
			break; //取不到数据 则 退出
		}
		flag =true
		for i:=2;i<num;i++{
    
    
			if num%i ==0{
    
    //证明是不是素数
				flag = false
				break
			}
		}
		if flag{
    
    //如果是素数则加到channel中
			primeChan<-num
		}

	}
	fmt.Println("一个协程由于取不到数据而退出")
	exitChan<-true//添加标识 到 退出 channel
}

Example of read-only and write-only coroutine

func test05(){
    
    
	//默认情况下 channel是可读可写
	//声明为只写
	var chan2 chan<- int
	chan2 = make(chan int,3)
	chan2<- 1
	chan2<- 2
	//num:=<-chan2 //error invalid operation: 
	//声明只读channel
	//var chan3 <-chan int
	//chan3 = make(chan int,3)
	//chan3<- 1 //invalid operation: 
	//num:=<-chan3 
	//fmt.Println("num=",num)  // deadlock!

	//协程测试 只读 和只写
	 var ch chan int
	 ch  = make(chan int,10)
	 exitChan := make(chan bool,2)
	 go send(ch,exitChan)
	 go recv(ch,exitChan)
	//  go func(){
    
    
	// 	for i:=1;i<=2;i++ {
    
    
	// 		val :=<-exitChan
	// 		fmt.Println("执行结束")
	// 	 }
	// 	 close(exitChan)
	//  }()
	total:=0
	for v := range exitChan{
    
    
		fmt.Println("读取内容v=",v)
		total ++
		if(total>= 2){
    
    
			break
		}
	}
	
	 fmt.Println("执行结束")

}
//该方法只写数据到 channel
func send(ch chan<- int,exitChan chan bool){
    
    
	fmt.Println("send 开始")
	for i:=1;i<=10;i++{
    
    
		ch<-i
	}
	close(ch)
	fmt.Println("send 结束")
	exitChan<- true
}
//该方法只读数据
func recv(ch <-chan int,exitChan chan bool){
    
    
	fmt.Println("recv 开始")
	for {
    
    
		v,ok:=<-ch
		if !ok{
    
    
			fmt.Println("读取值错误",ok)
			break
		}
		fmt.Println("读取到值==》:",v)
	}
	fmt.Println("recv 结束")
	exitChan<-true
}

select to solve the deadlock problem

func test06(){
    
    
   //select 解决从 管道 取数据的阻塞问题
   intChan :=make(chan int,10)
   for i:=0;i<10;i++{
    
    
	   intChan<-i
   }

   strChan :=make(chan string,5)
   for i:=0;i<5;i++{
    
    
	   strChan<-"hello"+fmt.Sprintf("%d",i)
   }

   //传统方式 遍历管道时 如果 没有关闭会导致阻塞 而报错 deadlock
   //实际开发过程中,可能不确实什么时候该关闭管道
   //可以使用select 解决报错的问题
   for{
    
    
	   select {
    
    
	   //这里 虽然intChan一直没有关闭,但是不会一直阻塞而deadlock,而是自动到写一个case匹配
	   		case v:=<-intChan:
				fmt.Printf("从intChan取值 %d \n",v)
				time.Sleep(time.Second)
			case v:=<-strChan:
				fmt.Printf("从strChan取值 %s \n",v)
				time.Sleep(time.Second)
			default:
				fmt.Printf("都没有取到值\n")
				return
	   }
   }

}

Coroutine exception handling

func test07(){
    
    
	go sayHello2()
	go testPanic()
	for i:=0;i<11;i++{
    
    
		fmt.Println("main ok~",i)
		time.Sleep(time.Second)
	}

}
func sayHello2(){
    
    
	for i:=0;i<10;i++{
    
    
		time.Sleep(time.Second)
		fmt.Println("helloWorld~")
	}
}
func testPanic(){
    
    
	//捕获异常
	//如果不捕获这个异常,那么主线程一起其他协程都会因异常而退出执行
	//捕获之后,该协程的异常不会影响其他协程和主线程
	defer func(){
    
    
		if err:=recover();err!=nil{
    
    
			fmt.Println("方法发生错误,",err)
		}
	}()
	var mapp map[int]string
	mapp[1]="golang"//没有执行make所以肯定报错
}

Guess you like

Origin blog.csdn.net/zhangxm_qz/article/details/115280572