并发编程,简单理解就GO了
[文章作者:磨延城 转载请注明原文出处: https://mo2g.com/view/140/ ]
每学习一门新的语言,都需要一段时间去适应他的语法、特性.或许一开始有点陌生,但是万变不离其宗,编程语言只是一种工具而已.所以写代码的时候,都是根据开发者的思路去编写代码,不同的编程语言,写出来的代码不一样,但思路都是相同的,这里的思路就是算法.
之所以学习GO语言,并不是PHP语言不好,只能说是个人兴趣爱好。每一门编程语言,都有合适他的使用场景。就目前看来,GO很适合并发编程。
什么是并发?下边举个简单的例子让大家感受一下。
校运会举行另类400米赛跑,参加比赛的班级以如下形式进行比赛:
A班:派出一名体育生,以52秒的成绩跑完全程,平均每100米需要13秒。
B班:派出4名男同学,以接力的形式跑完全程,平均每100米需要14秒,总耗时56秒。
C班:派出4名女同学,按接力的站位,同时起跑,平均每100米需要20秒,总耗时20秒。
从这个例子,应该很容易看出,即便是那些卡哇依的女同学,在并发编程的领导下,依然可以参加比赛。
每学习一门新的语言,都需要一段时间去适应他的语法、特性。或许一开始有点陌生,但是万变不离其宗,编程语言只是一种工具而已。所以写代码的时候,都是根据开发者的思路去编写代码,不同的编程语言,写出来的代码不一样,但思路都是相同的,这里的思路就是算法。
举个简单的例子,左手拿了2个气球,右手有3个气球,一共有几个气球?
2 + 3 = 5
是的,这是一道很简单的小学数学题,把他转化成运算公式,如下:
x + y = z
这就是算法,只不过不同编程语言,表达方式不一样而已。
所以为了快速学习GO语言的并发编程,我就尝试用GO语言来求解简单的数学题:
把1到max之间能被2或3或5或6整除的整数相加,求和。
我的思路就是:
1)把1到max的整数,分别除以 2、3、5、6,累计没有余数的整数
如果max为10,那就得到 2+3+4+5+6+8+9 = 37,GO语言代码如下:
package main import ( "fmt"//导入打印功能依赖的包 ) func main() { x := 0//初始化变量x为int x = _sum(1, 10) fmt.Println(x)//打印x的值 } func _sum(start int, end int) int { sum := 0 for i := start; i < end; i++ { if i%2 == 0 || i%3 == 0 || i%5 == 0 || i%6 == 0 { sum += i//累计满足条件的整数 } } return sum }
如果N值很大呢?思路也是一样的,只不过,我们可以在原有的基础上优化一下:
1)假设max为100,把1到100分成2份,比如 1~49,50~100
2)让A把1到50的整数,都分别除以 2、3、5、6,累计没有余数的整数
3)让B把51到100的整数,都分别除以 2、3、5、6,累计没有余数的整数
4)最后把A、B得出的数值相加一次
2)3)步骤就用到了GO语言的并发特性。
我们再试着把2)3)步骤完善一下,只要给出一个n值,就能把1~max分成n等份,分配给n个人来运算。这个思路是不是有点意思?
因为使用了并发的特性,所以得重构代码:
package main import ( "fmt" "runtime" ) func main() { runtime.GOMAXPROCS(2)//设定GO程序能用几个CPU进行运算 x := 0 x = go_sum(2, 10) fmt.Println(x) } /* num:把1~max分为几份 max:最大值 例如:go_sum(3,10) 把1~11分为3份,多出来的10就单独一份:123 456 789 10,num就变为了4份 */ func go_sum(num int, max int) int { sum, start, end := 0, 0, 0//初始化多个变量 y := max / num//因为max num都为int,所以运算会把小数点部分舍弃 if max%num != 0 { num += 1 } totle := make(chan int, num)//GO并发需要 for i := 0; i < num; i++ { //把1~max分成n等份,分配给n个人来运算 start = y * i end = y * (i + 1) if start == 0 { start = 1 } if end >= max { end = max } go chan_sum(start, end, totle)//分配运算任务 } for i := 0; i < num; i++ { sum += <-totle//累计运算结果 } return sum } func chan_sum(start int, end int, totle chan int) { sum := 0 for i := start; i < end; i++ { if i%2 == 0 || i%3 == 0 || i%5 == 0 || i%6 == 0 { sum += i } } totle <- sum }
上边的代码,我们用到了GO语言的并发特性,先不要去看那些“专业”的名词解释,简单理解就GO了:
1)totle := make(chan int, num) 创建能同时存储num个整数的空间totle
2)go chan_sum(start, end, totle) 使用for循环分配并发运算任务,并把结果存入totle,go关键字就是并发的开关
3)totle <- sum 把计算结果存入空间
4)sum += <-totle 使用for循环把空间里的数值读出来,并累计
GO默认使用一个CPU核心运行,runtime.GOMAXPROCS(n)可以设定使用的CPU个数。最好不要超过runtime.GOMAXPROCS(runtime.NumCPU()),CPU之间频繁的上下文切换会浪费时间。上面的代码,我设定了使用2个cpu。
大家肯定想知道这两种方法的效率对比,我给大家一个数据,1~10000000:
第一种方法耗时:24.003ms
第二种方法耗时:16.5021ms
最后就把耗时统计的代码贴出来,有兴趣可以自己测试一下。完整GO代码地址Github。
package main import ( "fmt" "runtime" "time" ) func main() { runtime.GOMAXPROCS(3) x := 0 time1 := time.Now() x = _sum(1, 10000000) time2 := time.Now() x = go_sum(2, 10000000) time3 := time.Now() fmt.Println(x) fmt.Println(time2.Sub(time1)) fmt.Println(time3.Sub(time2)) }
我来说两句: