南宁网站建设方案服务,乐达淄博网站建设制作,小皮怎么创建网站,深圳网站建设61916文章目录 一、goroutine(协程)1.goroutine入门2.goroutine基本介绍-1.进程和线程说明-2.程序、进程和线程的关系示意图-3.Go协程和Go主线程 3.案例说明4.小结5.MPG模式基本介绍6.设置Golang运行的CPU数7.协程并发#xff08;并行#xff09;资源竞争的问题8.全局互斥锁解决资… 文章目录 一、goroutine(协程)1.goroutine入门2.goroutine基本介绍-1.进程和线程说明-2.程序、进程和线程的关系示意图-3.Go协程和Go主线程 3.案例说明4.小结5.MPG模式基本介绍6.设置Golang运行的CPU数7.协程并发并行资源竞争的问题8.全局互斥锁解决资源竞争 二、管道1.为什么要使用channel2.channel的介绍3.管道的定义/声明channel4.channel使用的注意事项5.读写channel案例演示6.channel的遍历和关闭-1.channel的关闭-2.channel的遍历 7.管道阻塞的机制-1.应用实例2 --阻塞2-应用实例3 8.channel使用细节和注意事项1channel可以声明为只读或者只写性质2channel只读和只写的最佳实践案例3使用select可以解决从管道取数据的阻塞问题4goroutine中使用recover。解决协程中出现panic,导致程序崩溃问题 一、goroutine(协程)
1.goroutine入门
需求要求统计1-20000的数字中哪些是素数
分析思路
1传统的方法就是使用一个循环循环的判断各个数是不是素数
2使用并发或者并行的方式将统计素数的任务分配给多个goroutine去完成。这是就会使用到goroutine去完成这时就会使用goroutine
2.goroutine基本介绍
-1.进程和线程说明
1进程就是程序在操作系统中的一次执行过程是系统进行资源分配和调度的基本单位
2线程是进程的一个执行实例吗是程序执行的最小单位他是比进程更小的能独立运行的基本单位
3一个进程可以创建和销毁多个线程同一个进程中的多个线程可以并发执行
4一个程序至少有一个进程一个进程至少有一个线程
-2.程序、进程和线程的关系示意图 -3.并发和并行
1多线程程序在单核上运行就是并发
2多线程程序在多核上运行就是并行
并发因为是在一个CPU上比如有10个线程每个线程执行10毫秒进行轮换操作从人的角度来看好像这10个线程都在运行但是从微观上看在某一个时间点来看其实只有一个线程在执行这就是并发
并行因为是在多个CPU上(比如有10个CPU)比如有10个线程每个线程执行10毫秒各自在不同的CPU上执行从人的角度上看这10个线程都在运行但是从微观上看在某一个时间点也是同时有10个线程在执行这就是并行
-3.Go协程和Go主线程
1Go主线程有程序员直接称为线程/也可以理解成进程一个Go线程上可以起多个协程你可以这样理解协程是轻量级的线程
2Go协程的特点
有独立的栈空间共享程序堆空间调度由用户控制协程是轻量级的线程
示意图 3.案例说明
请编写一个程序完成如下功能
1在主线程可以理解成进程中开启一个goroutine,该协程每隔1秒输出hello world
2在主线程也每隔一秒输出hello golang,输出10次后退出程序
3要求主线程和goroutine同时执行
4画出主线程和协程执行流程图
代码实现
package main
import (fmtstrconvtime
)
/*
1在主线程可以理解成进程中开启一个goroutine,该协程每隔1秒输出hello world2在主线程也每隔一秒输出hello golang,输出10次后退出程序3要求主线程和goroutine同时执行
*/
//编写一个函数每隔1秒输出hello world
func test () {for i : 1; i 10; i {fmt.Println(test()hello worldstrconv.Itoa(i))time.Sleep(time.Second)}
}
func main() {go test() //开启了一个协程for i : 1; i 10; i {fmt.Println(main()hello worldstrconv.Itoa(i))time.Sleep(time.Second)}
}执行结果如下我们可以发现主线程和go协程是同时执行的 go主线程与go协程的执行示意图 4.小结
1主线程是一个物理线程直接作用在cpu上的是重量级的非常消耗cpu资源
2协程从主线程开启的是轻量级的线程是逻辑态。对资源消耗相对少
3golang的协程机制是重要的特点可以轻松开启上万个协程。其他编程语言的并发机制是一般基于线程的开启过多的线程资源耗费大这里就凸显出golang在并发上的优势了
5.MPG模式基本介绍 1M操作系统的主线程是物理线程
2P协程执行需要的是上下文
3G协程 6.设置Golang运行的CPU数
介绍为了充分利用多cpu的优势在Golang程序中设置运行的cpu数目
package main
import (fmtruntime
)func main() {//获取当前系统CPU的数量num : runtime.NumCPU()//我这里设置num -1的cpu运行go程序runtime.GOMAXPROCS(num)fmt.Println(num,num)
}1go1.8后默认让程序运行在多个核上可以不用设置了
2go1.8前还是要设置一下可以更高效的利用CPU了
7.协程并发并行资源竞争的问题
需求:现在要计算1-200的各个数的阶乘并且把各个数的阶乘放入到map中最后显示出来。要求使用goroutine完成
分析思路
1使用goroutine来完成效率高但是会出现并发/并行安全问题
2这里就提出了不同1goroutine如何通信的问题
代码实现
1使用goroutine来完成看看使用goroutine并发完成会出现什么问题
2在运行某个程序时如何知道是否存在资源竞争的问题方法很简单。在编译该程序时增加一个参数 -race即可 会发现map有些有值有些没有值各个协程出现了资源竞争的问题
3示意图 他们之间会出现资源竞争的问题
8.全局互斥锁解决资源竞争
不同的goroutine之间如何通信
1全局变量加锁同步
2channel
使用全局变量加锁同步改进程序
因为没有针对全局变量m加锁因此会出现资源竞争的问题代码会出现报错提示concurrent map writes
解决方案,-1加入互斥锁
package main
import (fmttimesync
)
//需求:现在要计算1-200的各个数的阶乘
// 并且把各个数的阶乘放入到map中最后显示出来。要求使用goroutine完成//思路
//1.编写一个函数来计算各个数的阶乘并放入到map中
//2.我们爱动的协程是多个统计的结果放入到map中
//2.map应该做出一个全局的var (myMap make(map[int]int,10) //声明一个全局的互斥锁//lock是一个全局的互斥锁//sync 是包synchornized 同步//Mutex是互斥的意思lock sync.Mutex
)//test函数就是计算n的阶乘将这个结果放入到map中
func test(n int) {res : 1for i :1; i n;i {res * i}//这里我们将res放入到myMap中//加锁lock.Lock()myMap[n] res//concurrent map writes//解锁lock.Unlock()
}func main() {//我们这里开启多个协程完成这个任务[200个协程]for i :1; i 15; i {go test(i)}//休眠10秒time.Sleep(time.Second * 5)//输出结果遍历结果lock.Lock()for i,v :range myMap {fmt.Printf(map[%d]%d\n,i,v)}lock.Unlock()
}我们的数的阶乘很大结果会越界我们可以改成sum uint64(i)
加锁解释 二、管道
1.为什么要使用channel
前面使用全局变量加锁同步来解决goroutine的通讯但不完美
1主线程在等待所有goroutine全部完成的时间很难确定。我们这里设置10秒仅仅只是估算
2如果主线程休眠时间长了会加长等待时间如果等待时间短了可能还有goroutine处于工作状态这时也会随着主线程的退出而销毁
3通过全局变量加锁同步来实现通讯也并不利用多个协程对全局变量的读写操作
4上面的种种分析都在呼唤一个新的通讯机制-channel
2.channel的介绍
1channel本质就是一个数据结构-队列
2数据是先进先出[FIFIO frist in first out]
3线程安全多goroutine访问时不需要加锁就是说在channel本身就是线程安全的
4channel是有类型的一个string的channel只能存放string数据 channel是线程安全多个协程作同一个管道时不会发生资源竞争的问题
3.管道的定义/声明channel
var 变量名 chan 数据类型
举例
var intChan chan int (intChan用于存放int数据)
var mapChan chan map[int]string (mapChan用于存放map[int]string类型)
var perChan chan Person
var perChan2 chan *Person
...说明
1channel是引用类型
2channel必须初始化才能写入数据即make后才能使用
3管道是有类型的 intChan只能写入整数int
管道的初始化写入数据到管道从管道读取数据以及基本的注意事项
package main
import (fmt
)
func main() {//演示一下管道的使用//1.创建一个可以存放3个int类型的管道var intChan chan intintChan make(chan int,3)//2.看看intChan是什么fmt.Printf(intchan的值是%v intChan本身的地址%p\n,intChan,intChan)//3.像管道写入数据intChan-10num : 211intChan- num//注意点当我们在给管道写入数据时不能超过其容量intChan- 50//intChan- 98 //会报错//4.输出看看管道的长度和cap容量fmt.Printf(channel len %v cap%v\n,len(intChan),cap(intChan)) // 3,3//5.从管道中读取数据var num2 intnum2 -intChanfmt.Printf(num2%v\n,num2) //10fmt.Printf(channel len %v cap%v\n,len(intChan),cap(intChan))//2,3//6.在没有使用协程的情况下如果我们的管道数据已经全部取出再取就会报告 deadlocknum3 : -intChannum4 : -intChan// num5 : -intChan// fmt.Println(num3,num3,num4,num4,num5,num5)//报错}
4.channel使用的注意事项
1channel中只能存放指定的数据类型
2channel的数据放满后就不能在放入了
3如果从channel取出数据后可以继续放入
4在没有使用协程的情况下如果channel数据取完了再取就会报deadlock
5.读写channel案例演示 package main
import (fmt
)type Cat struct {Name stringAge int
}func main() {//定义一个存放任意数据类型的管道3个数据// var allChan chan interface{}allChan : make(chan interface{},3)allChan-10allChan-tom jackcat :Cat{小花猫,4}allChan- cat//我们希望获得管道中的第三个元素则先将前2个推出
-allChan
-allChannewCat :-allChan //从管道中取出来的cat是什么
fmt.Printf(newCat%T,newCat%v\n,newCat,newCat)//newCatmain.Cat,newCat{小花猫 4}
//下面的写法是错误的,编译不通过则使用类型断言就可以通过
// fmt.Printf(newCat.Name%v,newCat.Name)
a :newCat.(Cat)
fmt.Printf(newCat.Name%v,a.Name)//newCat.Name小花猫
}
6.channel的遍历和关闭
-1.channel的关闭
使用内置函数close可以关闭channel,当channel关闭后就不能再向channel写数据了但是仍然可以从谈channel读取数据
package main
import (fmt
)
func main() {intChan :make(chan int,3)intChan- 100intChan- 200close(intChan) //close//这时不能够再写入到数channel//intChan- 300 //panic: send on closed channelfmt.Println(okok~)//当管道关闭后读取数据是可以的n1 : -intChanfmt.Println(n1,n1)
//输出如下// okok~//n1 100
}-2.channel的遍历
channel支持for-range的方式进行遍历请注意两个细节
1在遍历时如果channel没有关闭则会出现deadlock的错误
2在遍历时如果cahnnel已经关闭则会正常遍历数据遍历完成后就会退出遍历
代码演示
package main
import (fmt
)
func main() {intChan :make(chan int,3)intChan- 100intChan- 200close(intChan) //close//这时不能够再写入到数channel//intChan- 300 //panic: send on closed channelfmt.Println(okok~)//当管道关闭后读取数据是可以的n1 : -intChanfmt.Println(n1,n1)//遍历管道intChan2 :make(chan int,100)for i :0; i 100; i {intChan2 - i *2 //放入100个数据进去管道之中}//遍历:这种遍历是错误的因为遍历过程中管道的长度会变化// for i :0; i len(intChan2); {// }//在遍历时如果channel没有关闭则回出现deadlock的错误//在遍历时如果cahnnel已经关闭则会正常遍历数据遍历完成后就会退出遍历close(intChan2)for v : range intChan2 {fmt.Println(v,v)}
}7.应用案例
-1.应用案例1
请完成goroutine和channel协同工作案例具体要求
1开启一个writeData协程向管道intChan中写入50个整数
2开启一个readData协程从管道inChan中读取writeData写入的数据
3注意writeData和readData操作的是同一个管道
4主线程需要等到writeData协程都完成工作才能退出
思路分析 看代码演示
package main
import (fmt_time
)
//writeDtata
func writeData(intChan chan int) {for i :0;i50;i {//放入数据intChan- ifmt.Println(writeData,i)// time.Sleep(time.Second )}close(intChan)//关闭管道不影响读
}readDtata
func readData(intChan chan int,exitChan chan bool) {for {v,ok : -intChanif !ok {break}//time.Sleep(time.Second )fmt.Printf(readData 读到的数据%v\n,v)}//readData 读取完数据后即任务完成exitChan- true //数据读取完之后就网退出管道加入一个1close(exitChan)
}
func main() {//创建两个管道intChan : make(chan int,50)exitChan :make(chan bool,1 )go writeData(intChan)go readData(intChan,exitChan)//time.Sleep(time.Second * 10)for {_, ok :-exitChanif !ok {break}}
}7.管道阻塞的机制
-1.应用实例2 --阻塞
func main() {intChan :make(chan int, 10) //10-50的话数据一下就放入了exitChan :make(chan bool,1)//go readData(intChan,exitChan)//就是为了等待readData的协程完成for ——range exitChan{fmt.Println(ok...)}
}问题如果注销掉go readData(intChan, exitChan)程序会怎么样
答如果只是向管道写入数据而没有读取就会出现阻塞而deadLock原因是intChan容量是10而writeData会写入50个数据因此会阻塞在writeData的ch -i
2-应用实例3
1需求要求统计1 200000的数字中哪些是素数这个问题在本章开篇就提出了 现在我们有goroutine和channel的知识后就可以完成了[测试数据80000]
2分析思路 传统的方法就是使用一个循环循环的判断各个数是不是素数。 使用并发/并行的方式将统计素数的任务分配给多个4个)goroutine去完成, 完成任务时间短。
1.画出分析思路 2.代码实现
package main
import (fmttime
)
//向intChan放入 1-8000个数
func putNum(intChan chan int){for i : 0 ;i8000; i{intChan- i}//关闭intChanclose(intChan)
}//从intchan中取出数据并判断是否为素数,如果是就放入到primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){//使用for循环var flag boolfor {time.Sleep(time.Millisecond)num,ok : -intChanif !ok { //intChan取不到的时候就退出这个主for循环break}flag true //假设是素数//判断num是不是素数for i :2;inum;i{if num %i 0 { //说明i不是素数flag falsebreak}}if flag {//将这个数就放入到primeChan中primeChan- num}}fmt.Println(有一个协程因为取不到数据没退出)//这里我们还不能关闭primeChan//向exitChan 写入trueexitChan- true
}
func main() {intChan :make(chan int,1000)primeChan :make(chan int,2000) //放入结果//标识退出的管道exitChan :make(chan bool ,4) //4个//开启一个协程向intChan放入 1-8000个数go putNum(intChan)//开启4个协程从intchan中取出数据并判断是否为素数,如果是就放入到primeChanfor i :0;i4; i{go primeNum(intChan,primeChan,exitChan)}//这里我们主线程进行处理go func() {for i :0;i4; i{-exitChan}//当我们从exitChan祛除了4个结果就可以放心关闭primeChanclose(primeChan)}()//遍历primeChanfor {res,ok : -primeChanif !ok {break}//将结果输出fmt.Printf(素数为%d\n,res)}fmt.Println(main主线程退出)}说明使用goroutine完成后可以在使用传统的方法来统计一下看看完成这个 任务各自耗费的时间是多少?[用map保存primeNum]
使用go协程后执行的速度比普通方法提高至少4倍
8.channel使用细节和注意事项
1channel可以声明为只读或者只写性质
package main
import (fmt
)
func main() {//管道可以生命为只读或只写//1.在默认的情况下管道是双向的。// var chan1 chan int //可读可写//2.声明为只写var chan2 chan- intchan2 make(chan int,3)chan2- 20// num : -chan2 err在这个管道中不可以读fmt.Println(chan2,chan2)//3.声明为只读var chan3 -chan intnum2 : -chan3// chan3- 30 err 会报错因为该管道为只读fmt.Println(num2,num2)
}2channel只读和只写的最佳实践案例
package main
import (fmt
)//ch chan- int,这样ch就只能写操作
func send (ch chan- int,exitChan chan struct{}){for i :0; i 10; i {ch - i}close(ch)var a struct{}exitChan - a
}
//ch - chan int,这样ch就只能读操作了
func recv(ch -chan int,exitChan chan struct{}){for {v,ok : -chif !ok {break}fmt.Println(v)}var a struct{}exitChan - a
}
func main() {var ch chan intch make(chan int , 10)exitChan :make(chan struct{},2)go send(ch,exitChan)go recv(ch,exitChan)var total 0for _ range exitChan {total if total 2 {break}}fmt.Println(结束...)
}3使用select可以解决从管道取数据的阻塞问题
package main
import (fmttime
)
func main() {//使用select可以解决从管道读取数据阻塞问题//1.先定义一个管道 10个数据 intintChan :make(chan int, 10)for i : 0 ; i 10 ;i {intChan- i}//2.定义一个管道5个数据stringstringChan :make (chan string , 5)for i : 0; i 5 ; i {stringChan - hello fmt.Sprintf(%d,i)}//传统方法遍历管道时如果不关闭会阻塞而导致 deadlock//问题在实际开发中可能我们不好确定什么时候关闭该管道//可以使用select 方式解决label:for {select {case v : -intChan : //注意这里如果 intChan一直没有关闭不会导致deadlocks,会自动到下一个casefmt.Printf(从intChan读取的数据%d\n,v)time.Sleep(time.Second)case v : -stringChan :fmt.Printf(从stringChan读取的数据%s\n,v) time.Sleep(time.Second)default :fmt.Println(都取不到不玩了你可以加入逻辑) time.Sleep(time.Second)returnbreak label}}}4goroutine中使用recover。解决协程中出现panic,导致程序崩溃问题
package main
import (fmttime
)
//函数1
func sayHello() {for i : 0; i 10; i {time.Sleep(time.Second)fmt.Println(hello world)}
}
//函数2
func test(){//这里试用贴defer recoverdefer func() {//捕获test爬出的panicif err : recover(); err !nil {fmt.Println(test()发生错误,err)}}()//定义了一个mapvar myMap map[int]stringmyMap[0] golang //erro
}
func main(){go sayHello()go test()for i : 0; i 10; i {fmt.Println(main() ok,i)time.Sleep(time.Second)}
}输出结果如下 说明如果我们起了一个协程但…是这个协程出现了panic如果我们没有捕获这个panic。就会造成整个程序崩溃这时我们可以在goroutine中使用recover来捕获panic,进行处理这些即使这个协程发生的问题但是主线程任然不受影响可以继续运行