Article Directory
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
- The essence of channel is a data structure-queue, first in first out.
- The channel itself is thread-safe
- Channels are typed and can only store data of the same type as the declared type.
- The channel is a reference type and must be initialized (make) before writing data
- After the elements in the channel are filled, the dead lock exception will be reported when the elements are put
- Without using the coroutine, if the data in the chnnel has been fetched, deadlock will be reported after fetching
- If the channel is not closed during traversal, a deadlock exception will be reported. The reason should be the same as 6.
- 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所以肯定报错
}