@leowenyang
2015-10-24T11:54:40.000000Z
字数 6655
阅读 8634
go
转载说明: 本文章可以任意转载,但转载请说明出处和作者 @leowenyang
如有什么疑问,欢迎来信 leowenyang@163.com
依鲁卡:鸣人,今天老师教你一招新的忍术
鸣人: 什么忍术呀,酷不酷,厉害不厉害
依鲁卡: 分身术
鸣人: 分身术,把身体分离吗?,那我不就挂了。不学不学。。。
依鲁卡: 。。。
依鲁卡: 分身术是忍术中比较牛逼的一种忍术,学会者可以产生一个和本体一模一样的人,这样就可以两个人进行攻击
鸣人:产生一模一样的本体?是什么意思呀? 不明白
依鲁卡:简单的说就是,一个鸣人变成两个鸣人,两个鸣人同时找别人打架
鸣人: 两个鸣人,这个听起来真酷,我要学,我要学。
依鲁卡:好
依鲁卡: 下面我就把口诀教给你
下面进入GO 课堂
在go 语言中,只需要在函数前加一个关键字go, 就会产生一个协程(协程是go 语言维护的轻量级线程),新产生的协程会和原来的协程分开独立执行。
下面是产生新的新协程的例子
package main
func main() {
go nwRoutine()
}
func nwRoutine() {
}
上面的例子中,go nwRoutine()
会产生一个新的协程, 函数nwRoutine()
会在新的协程中和原来的协程main()
并发执行
GO 课堂结束
依鲁卡: 鸣人,口诀记住了吗?
鸣人: 记住了
依鲁卡:好吧,今天就到这里吧,你回家好好练习,下课
鸣人: 好的。老师再见
鸣人: 老师好
依鲁卡: 同学们好
依鲁卡: 鸣人,昨天的作业完成了吗?
鸣人: 作业? 什么作业? 现在都在减负呢,你不知道吗?
依鲁卡: 。。。
依鲁卡: 昨天教你的“分身术”你学的怎么样了?
鸣人: “分身术”,什么玩意,一点作用都没有,害的我白高兴了一天。老师就会糊弄我。
依鲁卡: 鸣。。。人。。。
依鲁卡: 好吧,你给展示一下,我看看到底是怎么个糊弄法。
鸣人: 那好,我就表演一下
下面进入GO 课堂
下面是一段go 程序,在main中输出I am Naruto
, 在新建的协程中输出I am Naruto too
package main
import "fmt"
func main() {
go nwRoutine()
fmt.Println("I am Naruto")
}
func nwRoutine() {
fmt.Println("I am Naruto too")
}
运行上述程序,输出
output : I am Naruto
没有输出 I am Naruto too
GO 课堂结束
鸣人: 老师你看,根本就分身不了
依鲁卡: 哈。。。哈。。。
鸣人: 还好意思笑。。。
依鲁卡: 鸣人,我给你说,不是忍术的问题,是你查卡拉控制的问题。你查卡拉发动忍术时,消失的太快,没有办法形成“分身”。
依鲁卡: 鸣人, 我告诉你个方法, 你可以这样做。。。
下面进入GO 课堂
在main 函数中增加 sleep()
package main
import "fmt"
import "time"
func main() {
go nwRoutine()
fmt.Println("I am Naruto")
time.Sleep(3*time.Second)
}
func nwRoutine() {
fmt.Println("I am Naruto too")
}
运行上述程序,输出
output : I am Naruto
output : I am Naruto too
GO 课堂结束
鸣人: 天哪,我的分身终于出现了
依鲁卡: 呵呵
依鲁卡:鸣人,回家好好练习吧,你的查卡拉越大,你生成的分身越多。你的查卡拉控制的越好,你的分身帮助你做的事情越好
依鲁卡:好吧,今天就到这里吧,下课
鸣人: 好的。老师再见
快上课了,依鲁卡提前走进了教室,教室里空无一人,依鲁卡静静的等待着同学们的到来
学生A: 慌慌张张的跑进教室,看见依鲁卡,哭着说,鸣人在我出家门的时候欺负我
学生B: 老师,我在吃饭的时候,鸣人抢走了我的饭
学生C: 老师,鸣人把我的玩具抢走了
。。。
学生D: 不好了,老师,门口来了十个鸣人。。。
学生E: 出大事了,出大事了,现在我们的木叶村到处都是鸣人了
依鲁卡: 大吃一惊,不好,鸣人的滥用分身术,一定是失控暴走了
依鲁卡: 大家都安静,都呆在教室里,不要出去,我去制服鸣人去
经过一番战斗,鸣人的分身都消失了,鸣人累的一动不动的躺在床上
三天后。。。
鸣人:缓缓的睁开眼,进入眼帘的是依鲁卡, 老师,我怎么了?
依鲁卡:鸣人,你滥用分身术,消耗了你太多的查卡拉,如果不是即时制止你的话,你有可能已经精尽人亡了
鸣人:啊,分身术这么烂
依鲁卡:不是分身术的问题,是你控制的问题,任何一个好的东西,都应该学会控制,如果滥用的话,会有严重的后果
鸣人:是这样呀,那我该怎么办呀?
依鲁卡:好,今天我就教你如何控制你的分身术
下面进入GO 课堂
下面是使用协程的一种常用方式
package main
import "fmt"
func main() {
nwServer()
fmt.Println("I am Naruto")
}
func nwServer() {
for {
go nwRoutine()
}
}
func nwRoutine() {
fmt.Println("I am Naruto too")
}
上一个方案会产生无数的协程,直到我们的资源被耗尽,应该使用下面的方式
package main
import "fmt"
const WORKER_NUM = 10
func main() {
nwServer()
fmt.Println("I am Naruto")
}
func nwServer() {
for i := 0; i < WORKER_NUM; i ++ {
go nwRoutine()
}
}
func nwRoutine() {
fmt.Println("I am Naruto too")
}
通过设置 WORKER_NUM
的值来控制可以产生的协程的数量,防止协程的暴走
GO 课堂结束
依鲁卡:从现在开始,你使用分身术,只可以产生三个分身,知道了吗?
鸣人:知道了
依鲁卡:好吧,你好好休息吧,我回学校了
鸣人:老师再见
上课铃声响了,依鲁卡慢慢的走进了教室,环顾了教室,看到同学们都到了,突然,一个人从座位上站了起来
鸣人:老师,分身术我已经完全掌握了,你看。。。(出现了三个鸣人)
鸣人:但我觉得分身术一点用处都没有
依鲁卡:为什么这么说呢?
鸣人:老师你看,虽然我有分身,但我的分身都不听我的指挥。这样不是等于没有吗?
依鲁卡:不听你指挥指的是什么呢?
鸣人:我给你展示一下,分身一,你去给我看看拉面店的老板在吗?
很长时间过去了,还是不见分身的踪影
鸣人:老师你看,我让分身一给我办点事,却一个结果都得不到
依鲁卡: 呵呵,鸣人,看来你已经开始思考了。不错,有进步
依鲁卡:看在你这么大的进步的事情上,今天我再教你点新东西
鸣人: 好呀,好呀,又有新东西学了
依鲁卡:分身术一个重要的秘诀是:本体和分身之间,分身和分身之间要进行沟通,只有沟通,他们才能协同的工作
鸣人: 沟通?如何沟通呢?
依鲁卡: 箱子
鸣人:箱子?什么箱子
依鲁卡:你准备一个箱子,本体和分身做事的结果就放到箱子里。这样通过查看箱子,你就能知道结果了
鸣人:听着好像有点道理呀
依鲁卡:是的,其实。。。
下面进入GO 课堂
在go 语言中,协程之间的通信,要使用基于消息的传递,而不要使用共享变量的方式。这种基于消息传递的方式,在go 中就是channel。 也就是说,在go 中,要“以通信来共享内存,而不要以共享内存来通信”
go 的channel 也可以来实现同步,因为channel可以产生阻塞,也就是说, 向channel中写入数据通常会导致程序的阻塞,直到有其他协程从这个channel中读取数据; 反之,亦可
下面是channel的例子
package main
import "fmt"
var box_channel = make(chan int)
func main() {
go nwRoutine()
result := <- box_channel
if result == 1 {
fmt.Println("I am Naruto")
}
}
func nwRoutine() {
fmt.Println("I am Naruto too")
box_channel <- 1
}
运行上述程序,输出
output : I am Naruto too
output : I am Naruto
看这个例子,已经不用使用sleep()函数了
GO 课堂结束
鸣人:我终于明白了
依鲁卡:鸣人,你用这个方法试一下上面的事情
鸣人:好的
鸣人:果然好使,同学们,大家快来吃拉面呀
依鲁卡:鸣人。。。
在一个风高月黑的夜晚,鸣人在床上辗转反侧,怎么也睡不着,他心中有一个疑问,一个很大很大的疑问,如果这个疑问得不到解答,我想他会失眠到天亮的。
突然,鸣人出床上蹦起来,一溜烟的冲到外面去了。他要去哪里?
鸣人来到依鲁卡家门外,胆战心惊的敲打着门,
屋里的灯亮了,传来了依鲁卡老师的声音
依鲁卡: 谁呀?
鸣人:依鲁卡老师,是我, 鸣人
依鲁卡: 是鸣人呀,这么晚了,你不去睡觉,找我有什么事情吗?
鸣人:依鲁卡老师,我心中有一个疑惑,想请教一下
依鲁卡:等到明天吧,我在课堂上给你解答, 你看好吗?
鸣人:不行呀,如果这个疑惑得不到解答,我今晚就睡不着觉了
依鲁卡: 。。。 那好吧,你就进来吧。
鸣人:依鲁卡老师,“分身术”有一个天大的弊端
依鲁卡:什么天大的弊端?说来听听
鸣人:“分身术”如果要使用通信的话,就等于“分身”失效了.
依鲁卡: 为什么怎么说呢?
鸣人: 你想呀,比如我要使用“分身”来买拉面,我派一个分身去,我的本体就不得不在实时的看着箱子,只有看到箱子里有了拉面了,我的本体才能做第二件事情,这样的话,不等于我只能做一件事吗?如果我想同时做两件事或者多件事的话,我该怎么办呢,就比如说,我想同时买拉面和烤串?
依鲁卡:呵呵,鸣人,很好,你想的没错
依鲁卡: 今天我再教你一个秘诀,解决你的疑惑
下面进入GO 课堂
在go语言中,channel初始化时是可以设置长度, 比如
var box_channel = make(chan int, 2)
这样当有 2
个协程正在处理请求时,不会发生阻塞,再有请求过来的话,才会出现被阻塞的情况
下面的代码,会出现同时有两个协程无阻塞的工作
package main
import "fmt"
import "time"
var box_channel = make(chan int, 2)
const WORKER_NUM = 10
func main() {
go nwServer()
time.Sleep(5 * time.Second)
result := <-box_channel
if result == 1 {
fmt.Println("I am Naruto")
}
}
func nwServer() {
for i := 0; i < WORKER_NUM; i++ {
go nwRoutine()
}
}
func nwRoutine() {
box_channel <- 1
fmt.Println("I am Naruto too")
}
运行上述程序,输出
output : I am Naruto too
output : I am Naruto too
output : I am Naruto
下面的代码,所有协程无阻塞的工作
package main
import "fmt"
var box_channel = make(chan int, 2)
const WORKER_NUM = 10
func main() {
go nwServer()
for result := range box_channel {
fmt.Println(result)
if result == 9 {
close(box_channel)
}
}
fmt.Println("I am Naruto")
}
func nwServer() {
for i := 0; i < WORKER_NUM; i++ {
go nwRoutine(i)
}
}
func nwRoutine(i int) {
box_channel <- i
fmt.Println("I am Naruto too")
}
GO 课堂结束
鸣人: 哦,原来可以这样呀, 我可以准备多个箱子,太简单了
依鲁卡: 呵呵,说了这么多,肚子都有点饿了,鸣人,有你的新的技巧去买一碗拉面和一碗刀削面吧
鸣人: 好的,马上让分身去完成
鸣人:最近比较烦,比较烦。。。
依鲁卡:谁在课堂上唱歌?
鸣人:是我,老师
依鲁卡:鸣人,你怎么回事?为什么要在课堂上唱歌呀?
鸣人:老师,我最近心情好差呀
依鲁卡:为什么?
鸣人:我的“分身”术又遇到瓶颈了
依鲁卡:什么瓶颈? 说来听听
鸣人:不说了,说多了都是泪呀
依鲁卡:呵呵,你不说,我怎么帮你呢
鸣人:好吧,那我就说说我的瓶颈
鸣人:本来认为分身可以高效的生活,但我发现我的生活一点都高效不起来
鸣人:记得有一次,我让我的分身去帮我买拉面,半天都没有回来,快把我饿死了。最后我不得不自己亲自去看看,发现原来拉面店没有开门,我的分身在傻乎乎的等呢。马尼,这不是饿死我的节奏吗?
鸣人:如果能给我的分身指定时间就好了
依鲁卡:说的好,今天我就教你如何设置时限
下面进入GO 课堂
Go 语言没有提供直接的超时处理机制,但可以利用select 机制,因为select的特点是只要其中一个case 已完成,程序就会继续向下执行,而不会考虑其他case的情况
package main
import "fmt"
import "time"
var box_channel = make(chan int)
func main() {
go nwRoutine()
select {
case <- box_channel:
fmt.Println("I am Naruto")
case <- time.After(time.Second):
fmt.Println("Time Out")
}
}
func nwRoutine() {
fmt.Println("I am Naruto too")
time.Sleep(3*time.Second)
box_channel <- 1
}
GO 课堂结束
鸣人: 太好了
鸣人:学会了这个,我再也不用担心迷失了
既然有了超时机制,那也需要一种机制来告知其他goroutine结束手上正在做的事情并退出。很明显,还是需要利用channel来进行交流,第一个想到的肯定就是向某一个chan发送一个struct即可。比如执行任务的goroutine在参数中,增加一个chan struct{}类型的参数,当接收到该channel的消息时,就退出任务。但是,还需要解决两个问题:
怎样能在执行任务的同时去接收这个消息呢?
如何通知所有的goroutine?
对于第一个问题,比较优雅的作法是:使用另外一个channel作为函数d输出,再加上select,就可以一边输出结果,一边接收退出信号了。
另一方面,对于同时有未知数目个执行goroutine的情况,一次次调用done <-struct{}{},显然无法实现。这时候,就会用到golang对于channel的tricky用法:当关闭一个channel时,所有因为接收该channel而阻塞的语句会立即返回。示例代码如下:
// 执行方
func doTask(done <-chan struct{}, tasks <-chan Task) (chan Result) {
out := make(chan Result)
go func() {
// close 是为了让调用方的range能够正常退出
defer close(out)
for t := range tasks {
select {
case result <-f(task):
case <-done:
return
}
}
}()
return out
}
// 调用方
func Process(tasks <-chan Task, num int) {
done := make(chan struct{})
out := doTask(done, tasks)
go func() {
<- time.After(MAX_TIME)
//done <-struct{}{}
//通知所有的执行goroutine退出
close(done)
}()
// 因为goroutine执行完毕,或者超时,导致out被close,range退出
for res := range out {
fmt.Println(res)
//...
}
}