1,goroutine-看一个需求
需求:要求统计1-90000000000的数字中,哪些是素数哦?
分析思路:
1)传统的方法,就是使用一个循环,循环的判断各个数是不是素数。
2)使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会用到goroutine
2,goroutine-基本介绍
2.1进程和线程介绍
2.2程序,进程和线程的关系示意图
2.3并发和执行
并发和并行
1)多线程程序在单核上运行就是并发
2)多线程程序在多核上运行就是并行
3)图示:
并发:一个cpu同时执行多个线程
并行:多个cpu上执行多个线程,就相当于一个cpu执行一个线程
2.4GO协程核GO主线程
GO主线程(有程序员直接称为线程/也可以理解为进程):一个GO线程上,可以有多个协程,你可以理解为协程是轻量级的线程[编译器做的优化]
GO协程的特点
1)有独立的栈空间
2)共享程序堆空间
3)调度由用户控制
4)协程是轻量级的线程
3,goroutine-简单例子
3.1案例说明
请编写一个程序,完成如下功能
1)在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔一秒输出一次helloworld“
2)在主线程中也每隔一秒输出”hellogolang“,输出10次后,退出程序
3)要求主线程和goroutine同时执行
4)画出主线程和协程的执行流程图
主线程和协程执行流程图
3.2小结
1)主线程是一个物理线程,直接作用在cpu上。是重量级的,非常耗费cpu资源
2)协程从主线程开启,是轻量级的线程,是逻辑态。对资源的耗费相对较小
3)golang的协程机制是重要的特点,可以轻松的开启上万个协程。其他语言的并发机制是一般基于线程的,开启过多的线程耗费资源大,这里就凸显出golang的优势了
4,goroutine的调度模型
4.1MPG模式基本介绍
1)M:操作系统的主线程(是物理线程)
2)P:协程执行需要的上下文
3)G:协程
4.2MPG模式运行的状态1
1)当前程序有三个M,如果三个M都在一个CPU运行,就是并发,如果在不同的CPU下运行就并行
2)M1,M2,M3正在执行一个G,M1的协程队列有三个,M2的协程队列有三个,M3的协程队列有两个
3)从上图可以看出:GO的协程是轻量级的线程,是逻辑态的,GO可以容易起上万个协程
4)其他程序c/java的多线程往往是内核态的,比较重量级,几千个线程可能耗光CPU
4.3MPG模式运行的状态2
1)分成两部分来看
2)原来的情况是M0主线程正在执行G0线程,另外有三个线程在队列等待
3)如果G0线程阻塞,比如读取文件或者数据库等
4)这时就会创建出m1主线程(也可能是从已经有的线程中取出M1)并且将等待的3个协程挂到M1下开始执行,M0的主线程下的G0仍然执行io的读写
5)这样的MPG调度模式,可以既让G0执行同时也可以让队列的其他的协程执行,仍然可以并发执行
6)等到G0不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中获取),同时G0又会被唤醒
5,设置Golang运行的CPU数
说明:为了充分利用多cpu的优势,在Goalng程序中,设置cpu数目
6,channel(管道)-需求
需求:现在要计算1-200各个数的阶乘,并且把各个数的阶乘放到map中。最后显示出来,要求使用goroutine来完成
思路:
1)使用goroutine来完成,效率高,但是会出现并发/并行安全问题
2)这里就提出了不同goroutine如何通信的问题
代码实现:
1)使用goroutine来完成(看看goroutine并发完成会出现什么问题)
2)在运行某个程序时,如何知道是否存在资源竞争问题。在编译程序时,增加一个参数-race即可
- package Goroutine
-
- import (
- "fmt"
- "time"
- )
-
- var(
- myMap=make(map[int]int,10)
- )
-
- //计算n!并放入到map里
- func operation(n int) {
- res:=1
- for i:=1;i<=n;i++{
- res*=i
- }
- myMap[n]=res
- }
-
- func Test3() {
- //我们开启多个协程去完成这个任务
-
- for i:=1;i<=200;i++{
- go operation(i)
- }
-
- time.Sleep(time.Second*10)
-
- fmt.Println(myMap)
- }
会有错误产生
6.1不同goroutine之间如何通讯
1)全局变量互斥锁
2)使用channel来解决
6.2使用全局变量加锁同步改进程序
因为没有对全局变量加锁,因此会出现资源争夺的问题,代码会出现错误,提示
解决方案:加入互斥锁
我们的数阶乘很大,将数改为uint64()
- package Goroutine
-
- import (
- "fmt"
- "sync"
- "time"
- )
-
- var(
- myMap2=make(map[int]uint64,10)
- //声明一个全局互斥锁,lock是一个全局互斥锁,sync是包:synchorized同步
- //Mutex:是互斥
- lock sync.Mutex
- )
- func operation2(n int) {
- var res uint64=1
- for i:=1;i<=n;i++{
- res*=uint64(i)
- }
- //我们将res放入myMap
- //加锁
- lock.Lock()
- myMap2[n]= res
- //解锁
- lock.Unlock()
- }
-
- func Test4() {
- for i:=1;i<=200;i++{
- go operation2(i)
- }
-
- time.Sleep(time.Second*10)
- //这里我们输出
- //加锁
- lock.Lock()
- fmt.Println(myMap2)
- lock.Unlock()
- }
6.3为什么需要channel
1)前面使用全局变量加锁同步来解决goroutine的通讯并不完美
2)主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算
3)如果主线程休眠时间长了,会加长等待时间,如果时间短了,可能还有goroutine处于工作状态,这时也会随着主线程的退出而销毁
4)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量进行读写操作
5)下面来讲以下channel
6.4 channel的基本介绍
1)channel本质是一个数据结构+队列
2)数据是先进先出
3)线程安全,多个goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
4)channel有类型的,一个string的channel只能存放string类型的数据
6.5定义/声明管道
var 变量名 chan 数据类型
举例:
var intChan chan int 存放int类型
var mapChan chan map[int]string mapChan存放map[int]string类型
var perChan chan Person...
说明
channel是引用类型
channel必须初始化才能写入数据,即make后才能用
管道是有类型的,intChan只能写入整数int
6.6管道的初始化,写入数据到管道,从管道读取数据,注意事项
- package Goroutine
-
- import "fmt"
-
- func Test5() {
- //演示一下管道的使用
- //1,创建一个可以存放3个int类型的管道
- var intChan chan int
- intChan=make(chan int,3)
- //2,看看intChan是什么
- fmt.Printf("值:%V 地址:%p\n",intChan,&intChan)
- //3,向管道写入数据
- intChan<-10
- num:=211
- intChan<-num
- intChan<-50
- //intchan<-99注意不要超过它的容量
- //4,看看管道的长度和cap(容量)
- fmt.Printf("长度 len=%v cap=%v\n",len(intChan),cap(intChan))
- //5,从管道里读取数据
- var num2 int
- num2=<-intChan
- fmt.Println("num2=",num2)
- fmt.Printf("长度 len=%v cap=%v\n",len(intChan),cap(intChan))
- //6,在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告deadlock
- num3:=<-intChan
- num4:=<-intChan
- num5:=<-intChan
- fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)
-
- }
6.7channel使用的注意事项
1)channel只能存放指定的数据类型
2)channel的数据放满后,就不能再放入了
3)如果从channel取出数据后就可以继续放入
4)在没有使用协程的情况下,如果channel数据取完了,再取就会报错
6.8读写channel案例演示
1)创建一个intChan,最多可以放3个int,演示存三个数据到intChan然后再取出这三个int
2)创建一个mapChan,最多可以存放1个map[string]string演示写入和读取
3)创建一个结构体变量cat,创建一个管道,演示catChan的存取
4)创建一个allchan可以存放任意数据类型的变量
7,练习
1)创建一个person结构体[Name,Age,Address]
2)创建10个Person实例,并放入到channel中
3)遍历channel,将各个person实例信息显示在终端
- package Goroutine
-
- import "fmt"
-
- type Person struct {
- name string
- Age int
- Address string
- }
- func Test9() {
- var PersonChan chan Person
- PersonChan=make(chan Person,10)
- for i:=0;i<10;i++{
- PersonChan<-Person{"wang",i,"asa"}
- }
- for len(PersonChan)!=0{
- fmt.Println(<-PersonChan)
- }
-
- }
8,chaneel的遍历和关闭
8.1channe的关闭
使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据
8.2channel的遍历
channel支持for-range的方式进行遍历
1)在遍历时,如果channel没有关闭,则会出现deadlock错误
2)在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后就会退出遍历
8.3channel遍历和关闭的案例演示
8.4应用实例1
请完成goroutine和channel协同工作的案例
1)开启一个writeData协程,向管道intChan中写入五十个数
2)开启一个readDate协程,从管道intChan中读取writeData写入数据
3)注意:WriteData和readData操作的是同一个管道
4)主线程需要等待writeData和readData协程都完成工作才能退出管道
- package Goroutine
-
- import (
- "fmt"
- )
-
- //writeData
- func writeData(intChan chan int) {
- for i:=1;i<=50;i++{
- intChan<-i
- fmt.Println("writeData",i)
- }
- close(intChan)
- }
- //readData
- func readData(intChan chan int,exitChan chan bool) {
- for{
- v,ok:=<-intChan
- if !ok{
- break
- }
- fmt.Println("readData",v)
- //time.Sleep(time.Second)
- }
-
- exitChan<-true
- close(exitChan)
- }
-
- func Test12() {
- //创建两个管道
- intChan:=make(chan int,50)
- exitChan:=make(chan bool,1)
- go writeData(intChan)
- //time.Sleep(time.Second)
- go readData(intChan,exitChan)
- //time.Sleep(time.Second)
- for{
- _,ok:=<-exitChan
- fmt.Println("sa")
- if!ok{
- break
- }
- }
- }
8.5应用实例2-阻塞
如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock,原因是intChan容量是10,而代码writeData会写入50个数据,因此会阻塞在writeData的ch<-i
8.6应用实例3
要求统计1-200000的数字中,哪些是素数?我们这里采用goroutine和channnel来完成,测试数据是80000
分析思路:
传统的方法是一个循环,判断各个数字是不是素数
现在使用并发知识,将统计素数的任务分配给4个goroutine去完成
- package Goroutine
-
- import (
- "fmt"
- "time"
- )
-
- //向intChan放入1-8000个数字
- func putNum(intChan chan int){
- for i:=1;i<=8000;i++{
- intChan<-i
- }
- //关闭intChan
- close(intChan)
- }
- //从intChan中取出数据,并判断是不是素数,如果是就放入到primeChan
- func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){
- var flag bool
- for{
- time.Sleep(time.Millisecond*10)
- num,ok:=<-intChan
- if!ok{//intChan取不到
- break
- }
- flag=true
- for i:=2;i<num;i++{
- if num%i==0{
- flag=false
- break
- }
- }
- if flag{
- //就将这个数放入到primeChan
- primeChan<-num
- }
- }
- fmt.Println("有一个primeNUM因为取不到数据,退出")
- //向exitChan写入true
- exitChan<-true
-
- }
- func Test13() {
- intChan:=make(chan int,1000)
- primeChan:=make(chan int,2000)//放入结果
- //标识退出管道
- exitChan:=make(chan bool,4)
- go putNum(intChan)
- //开启四个协程
- for i:=0;i<4;i++{
- go primeNum(intChan,primeChan,exitChan)
- }
- //这里我们主线程进行处理
- //直接
- go func() {
- for i:=0;i<4;i++{
- fmt.Println("exitchan",<-exitChan)
- }
- //当我们从exitChan中取出来四个结果后,就可以放心的关闭prprimeChan
- close(primeChan)
- }()
-
- //遍历我们的primeChan
- for{
- res,ok:=<-primeChan
- if !ok{
- break
- }
- fmt.Println("素数",res)
-
- }
- fmt.Println("main线程退出")
- }
9,channel使用细节和注意事项
1)channel可以声明为只读,或者只写性质
2)channel只读和只写的最佳实践案例
3)使用select可以解决从管道读取数据的阻塞问题
- package Goroutine
-
- import (
- "fmt"
- "time"
- )
-
- func Test15() {
- //1,定义一个管道存10个数据
- intChan:=make(chan int,10)
- for i:=0;i<10;i++{
- intChan<-i
- }
- //2,定义一个管道存5个数据string
- stringChan:=make(chan string,5)
- for i:=0;i<5;i++ {
- stringChan<-"sww"+fmt.Sprintf("%d",i)
- }
- //在传统的方法中我们不关闭会阻塞而导致deadlock
- //我们可以使用select方式解决
- for {
- select{
- //注意,这里如果intchan一直没有关闭,不会一直阻塞而deadlock,会自动到下一个case匹配
- case v:=<-intChan:
- fmt.Println("数据",v)
- time.Sleep(time.Second)
- case v:=<-stringChan:
- fmt.Println("字符串",v)
- time.Sleep(time.Second)
- default:
- fmt.Println("都取不到了")
- return
- }
- }
- }
4)goroutine中使用recover解决协程中出现的panic,导致程序出现问题,这时我们可以在goroutine中使用recover来捕获panic进行处理,这样即使这个协程发生问题,但是主线程仍然不受影响,可以继续执行
- package Goroutine
-
- import (
- "fmt"
- "time"
- )
-
- func sayHello() {
- for i:=0;i<10;i++{
- time.Sleep(time.Second)
- fmt.Println("hello world")
- }
- }
- //函数
- func errorrecover(){
- //这里我们可以使用defer+recover
- defer func() {
- //捕获test抛出的panic
- err:=recover()
- if err!=nil{
- fmt.Println("test()发生错误",err)
- }
- }()
- var m1 map[int]string
- m1[0]="ss"
- }
-
- func Test16() {
- go sayHello()
- go errorrecover()
- for i:=0;i<10;i++{
- fmt.Println(i)
- time.Sleep(time.Second)
- }
- }
码字不易,还望点个赞,点个关注多多支持一下!谢谢!
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !