关于golang的goroutine

go语言原生支持并发编程 

在通用型语言中 原生支持并发编程的并不常见

下面我们先来看一个例子

package main

import (
	"fmt"
	"time"
)

func main(){
	for i := 0; i < 1000; i ++ {   //一千个人同时运行
		go func(it int){   //go关键字支持并发运行
			for {
				fmt.Println("hello wold!", it)  //运行的目的是打印这句话
			}
		}(i) //将i传入匿名函数 直接在匿名函数中使用i会造成程序崩溃 下面会详细讲到
	}
	time.Sleep(time.Millisecond) //这里给他一个限制 只允许运行一毫秒
}

go语言的并发关键字就是go

后面跟需要并发的函数名称 但一般情况下使用匿名函数

 

需要注意的是 当main函数退出时 所有并发都会被杀掉 以这个例子来说time.sleep就是让main函数延迟1毫秒退出

 

例子中是让1000个人同时打印 对一般编程语言而言(不算第三方) 相同环境下已经比较吃力 而go语言还可以增加更多

这主要得益于go语言的关键字go 并发处理开的是协程(Coroutine)而不是线程 

协程是轻量级线程 粗略来说 协程和线程相同 都是处理并发开多个线程的 但是协程是轻量级的

 

协程为什么是轻量级的 下面列出几个关键点

 

非抢占式多任务处理,由协程主动交出控制权

       线程在任何时候都可以被系统切换 所有线程又称之为抢占式多任务处理 哪怕程序执行到一半 都可以被系统切换

       协程是主动交出控制权 这是协程轻量级的关键

编译器/解释器/虚拟机层面的多任务

       协程可以看作是编译器层面的多任务而不是操作系统的多任务 操作系统只有线程没有协程

       具体是go语言在执行goroutine时 编译器会有一个调度器为他调度协程 操作系统有调度器 go语言有自己的调度器

多个协程可以在同一个或者多个进程上运行

 

下面我们来看一个协程不主动交出控制权的代码

func main(){
	var a[15]int
	for i := 0; i <15; i ++ {
		go func(i int){
			for {
				a[i]++ //死循环 无法交出控制权
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}

不交出控制权的后果就是其他线程都在等待它交出控制权(交出控制权不代表线程结束)所以这是一个死循环

 

那我们有什么办法让他主动交出控制权呢 除了调用 io.函数之外(fmt.print...之类的函数就是调用io.)

那就是在循环结尾加 runtime.Gosched()

func main(){
	var a[15]int
	for i := 0; i <15; i ++ {
		go func(i int){
			for {
				a[i]++ //死循环 无法交出控制权
                runtime.Gosched()
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}

runtime.Gosched()相当于从火车上离开 让给别的乘客(线程)但是自身可以继续乘坐火车 只要自身需要

大部分情况下都不会用到runtime.Gosched()  

 

从上面代码我们可以发现 循环i值是作为参数进入匿名函数的 为什么不直接使用i值呢 

因为当我们匿名函数直接使用i值时会报错

func main(){
	var a[15]int
	for i := 0; i <15; i ++ {
		go func(){
			for {
				a[i]++
				runtime.Gosched()
			}
		}()
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}

关于golang的goroutine_第1张图片

我们使用上面命令行代码可以打印出错误报告 

当运行for循环最后一次时 i还有一次++ 数组a[i]就会越界 所以报错

 

我们继续引入一个新的概念

子程序是协程的一个特例 每一次调用函数时就是开协程

普通函数时如何运行的呢

            普通函数

-线程----------------------------------------

|                                                       |

|                       main                        |

|                        \|/                            |

|                    dowork                       |

|                                                       |

|                                                       |

-----------------------------------------------

在一个线程内 main调用子函数 子函数运行结束后交出控制权  这里采用的是main单项传输到dowork

 

协程是如何运作的呢

            普通函数

-线程(可能多个)------------------------

|                                                       |

|                       main                        |

|                           /|\、                     |

|                           \|/                         |

|                      dowork                      |

|                                                       |

-----------------------------------------------

协程是一组双向通道 控制权可以互通 双方可以不在一个线程 只有这样才能保证并发的数量

至于几个协程共用一个线程是如何计算的 不归我们管 这是调度器自己控制的

           goroutine可能的切换点

.I/O,select

.channel

.等待锁

.函数调用(有时)

.runtime.Gosched()

只是参考,不能保证切换,不能保证在其他地方不切换

 

          总结goroutine

.任何函数只要加上go 就能送给调度器运行

.不需要在定义时 区分是否异步

.调度器在合适的点进行切换 人为不能完全控制

.使用-race检测数据访问冲突

 

最后一句话总结就是go语言的线程是非抢占式

你可能感兴趣的:(golang,golang,并发,线程,协程)