资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Java

goroutine

Java 更新时间: 发布时间: 计算机考试归档 最新发布

goroutine

目录

  • 1.基础知识
    • 并发与并行
    • 进程和线程
    • go协程(goroutine)与go主线程
    • go协程特点
    • goroutine退出机制
    • goroutine的MPG(执行模式
    • goroutine运行范例
    • 设置CPU个数
  • 2. 实例:goroutine实现 1-50 的各个数的阶乘的计算

1.基础知识

并发与并行

(从线程与CPU的关系角度分析)
并发:多个线程在单核上运行,宏观上同时运行,微观上某一时刻只有一个线程在运行
并行:多线程在多核上运行,无论宏观上还是微观上,某一时刻有多个线程在同时运行

进程和线程

进程:程序在操作系统中一次执行过程,系统进行资源分配和调度的基本单位
线程:进程的一个执行实例,是程序执行的最小单元,比进程更小的能独立运行的基本单位
一个进程创建和销毁多个线程,同一个进程中的线程可以并发执行

go协程(goroutine)与go主线程

一个Go主线程(线程或进程)可以起多个协程;

主线程是物理线程,直接对CPU资源划分,耗费CPU;

协程是主线程开启的轻量级线程,属于逻辑态,资源消耗小,不直接作用CPU,可以轻松开启上万个协程;

  • 协程的数量与运行效率提升也不是完全成正比,因为CPU资源与运算速率也是有限的,当CPU跑满之后,开启再多的线程也无法提升效率。这也是golang的一种并发优势。其他编程语言一般是基于线程的。

  • 运行时主线程与协程同时执行(多核情况下并行,单核情况下,并发) 主线程退出了,即使协程还没有执行完毕,也会退出

go协程特点

  1. 协程–轻量级的线程
  2. 调度由用户控制:
    用户可以控制协程之间的调度顺序(关系),而线程之间的调度关系是由操作系统控制的,用户无法操控
  3. 有独立的栈空间
  4. 共享程序堆空间

goroutine退出机制

主线程退出,协程退出
协程执行完毕后自己退出

goroutine的MPG(执行模式

G: Goroutine,即我们在 Go 程序中使用 go 关键字创建的执行体;
M: Machine,或 worker thread,即传统意义上进程的线程;
P: Processor,即一种人为抽象的、用于执行 Go 代码被要求局部资源。只有当 M 与一个 P 关联后才能执行 Go 代码。除非 M 发生阻塞或在进行系统调用时间过长时,没有与之关联的 P。
https://golang.design/under-the-hood/zh-cn/part2runtime/ch06sched/mpg/

当一个协程G0在主线程M0上出现阻塞时,会重新创建一个新的主线程M1(或从已有的线程池中取出一个线程M1)执行后面的协程G1,这种调度模式,可以让G0执行,同时避免了后面的协程阻塞,实现并发/并行。

goroutine运行范例

package mainimport (	"fmt"	"strconv"	"time")// 在主线程(可以理解成进程)中,开启一个goroutine, 该协程每隔1秒输出 "hello,world"// 在主线程中也每隔一秒输出"hello,golang", 输出10次后,退出程序// 要求主线程和goroutine同时执行//编写一个函数,每隔1秒输出 "hello,world"func test() {	for i := 1; i <= 10; i++ {		fmt.Println("tesst () hello,world " + strconv.Itoa(i))		time.Sleep(time.Second)	}}func main() {	go test() // 开启了一个协程	for i := 1; i <= 10; i++ {		fmt.Println(" main() hello,golang" + strconv.Itoa(i))		time.Sleep(time.Second)	}}
// output main() hello,golang1tesst () hello,world 1tesst () hello,world 2 main() hello,golang2tesst () hello,world 3 main() hello,golang3 main() hello,golang4tesst () hello,world 4tesst () hello,world 5tesst () hello,world 8tesst () hello,world 9 main() hello,golang9 main() hello,golang10tesst () hello,world 10

设置CPU个数

package mainimport (	"fmt"	"runtime")func main() {	cpuNum := runtime.NumCPU()	fmt.Println("cpuNum=", cpuNum)	//可以自己设置使用多个cpu	runtime.GOMAXPROCS(cpuNum - 1)	fmt.Println("ok")}

2. 实例:goroutine实现 1-50 的各个数的阶乘的计算

需求:现在要计算 1-50 的各个数的阶乘,并且把各个数的阶乘放入到map中, 最后显示出来。要求使用goroutine完成

思路:

  1. 编写一个函数,来计算各个数的阶乘,并放入到 map中.
  2. 我们启动的协程多个,统计的将结果放入到 map中
  3. map 应该做出一个全局的.
package mainimport (	"fmt"	"sync")var (	myMap = make(map[int]int, 10))// test 函数就是计算 n!, 让将这个结果放入到 myMapfunc test(n int) {	res := 1	for i := 1; i <= n; i++ {		res *= i	}	//这里我们将 res 放入到myMap	myMap[n] = res  //出现错误 concurrent map writes?  多个协程对map空间并发写入,}func main() {	// 开启多个协程完成任务[50个]	for i := 1; i <= 50; i++ {		go test(i)	}	//这里我们输出结果,变量这个结果	for i, v := range myMap {		fmt.Printf("map[%d]=%dn", i, v)	}}

不会有任何输出
原因:协程未执行完毕时,主线程已经退出,所以没有任何输出
解决方案1:加锁,如下所示

// 方案1:加锁package mainimport (	"fmt"	"sync"	"time")var (	myMap = make(map[int]int, 10)	//声明一个全局的互斥锁	//lock 是一个全局的互斥锁,	//sync 是包: synchornized 同步	Mutex : 是互斥	lock sync.Mutex)// test 函数就是计算 n!, 让将这个结果放入到 myMapfunc test(n int) {	res := 1	for i := 1; i <= n; i++ {		res *= i	}	//这里我们将 res 放入到myMap	//加写锁	lock.Lock()	myMap[n] = res //concurrent map writes?	解锁	lock.Unlock()}func main() {	// 开启多个协程完成任务[50个]	for i := 1; i <= 50; i++ {		go test(i)	}		//休眠5秒钟	time.Sleep(time.Second * 5)	//加读锁;为什么?	lock.Lock()	//这里我们输出结果,变量这个结果	for i, v := range myMap {		fmt.Printf("map[%d]=%dn", i, v)	}	lock.Unlock()}

如果不加写锁,将不会有有任何输出,协程没有执行完毕,主线程就执行结束,所以协程也跟着结束
func main 不加读,运行 go build -race main.go
结果出现数据竞争 :Found 2 data race(s) (示例如下)
原因分析:main函数读取部分为什么需要加互斥锁,按理说5秒数上面的协程都应该执行完,后面就不应该出现资源竞争的问题了,但是在实际运行中,还是可能在红框部分出现(运行时增加-race参数,确实会发现有资源竞争问题),因为我们程序从设计上可以知道5秒就执行完所有协程,但是主线程并不知道,因此底层可能仍然不断尝试读取,从而出现资源争夺,因此加入互斥锁即可解决问题

不添加读锁,仍然会有数据竞争

//不添加读锁,仍然会有数据竞争map[27]=-5483646897237262336map[29]=-7055958792655077376map[36]=9003737871877668864map[48]=-5844053835210817536map[24]=-7835185981329244160map[18]=6402373705728000map[21]=-4249290049419214848map[22]=-1250660718674968576map[25]=7034535277573963776map[38]=4789013295250014208map[42]=7538058755741581312map[49]=8789267254022766592map[14]=87178291200map[50]=-3258495067890909184map[15]=1307674368000map[19]=121645100408832000map[26]=-1569523520172457984map[28]=-5968160532966932480map[41]=-2894979756195840000map[43]=-7904866829883932672map[44]=2673996885588443136map[2]=2map[46]=1150331055211806720map[11]=39916800map[16]=20922789888000map[33]=3400198294675128320map[34]=4926277576697053184map[7]=5040map[13]=6227020800map[23]=8128291617894825984map[30]=-8764578968847253504map[35]=6399018521010896896map[47]=-1274672626173739008map[8]=40320map[6]=720map[10]=3628800map[3]=6map[4]=24map[5]=120map[20]=2432902008176640000map[31]=4999213071378415616map[32]=-6045878379276664832map[37]=1096907932701818880map[39]=2304077777655037952map[1]=1map[45]=-8797348664486920192map[40]=-70609262346240000map[12]=479001600map[17]=355687428096000map[9]=362880Found 2 data race(s)exit status 66

同时添加读写锁,无竞争冲突

//同时添加读写锁,无竞争冲突map[8]=40320map[21]=-4249290049419214848map[17]=355687428096000     map[13]=6227020800          map[39]=2304077777655037952 map[5]=120                  map[25]=7034535277573963776 map[38]=4789013295250014208 map[29]=-7055958792655077376map[40]=-70609262346240000  map[44]=2673996885588443136 map[3]=6                    map[7]=5040                 map[24]=-7835185981329244160map[26]=-1569523520172457984map[35]=6399018521010896896 map[47]=-1274672626173739008map[33]=3400198294675128320 map[27]=-5483646897237262336map[1]=1                    map[2]=2                    map[10]=3628800             map[15]=1307674368000       map[20]=2432902008176640000 map[19]=121645100408832000  map[42]=7538058755741581312map[46]=1150331055211806720map[4]=24map[16]=20922789888000map[14]=87178291200map[32]=-6045878379276664832map[31]=4999213071378415616map[28]=-5968160532966932480map[36]=9003737871877668864map[34]=4926277576697053184map[9]=362880map[11]=39916800map[12]=479001600map[22]=-1250660718674968576map[23]=8128291617894825984map[41]=-2894979756195840000map[6]=720map[18]=6402373705728000map[30]=-8764578968847253504map[43]=-7904866829883932672map[45]=-8797348664486920192//没有数据竞争,但存在数据溢出

加锁的主要目的是为了解决协程与主线程的通讯问题,那么,如何让主程序自动判定协程是否结束?channel是更好的解决方案。

参考: 【尚硅谷】Golang入门到实战教程丨一套精通GO语言 P264–P271

转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/1097032.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【goroutine】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2