1.基于Golang协程实现流量统计系统-协程的学习

最近开始学习基于Golang协程实现流量统计系统之协程这套视频,把每天学习的内容总结成文章,第一方便今后复习使用,另外还让想学习的兄弟学习

这是一个例子1:

这就是最简单的goroutine的例子,单协程版

功能:

用goroutine往通道中放入一串字符串

然后从通道中取出该字符串输出打印出来

package main

import (
"fmt"
)

func main()  {
var message = make(chan string) //创建一个通道,单通道,只能1进1出
go func() { //创建一个goroutine
message <-"hello goroutine1" //往通道中写入字符串,此时通道阻塞
}()

fmt.Println(<-message) //从通道中取出数据,此时取出的是刚才写入的字符串,然后输出打印出来。
fmt.Println("helloworld!")
}

输出结果:

hello goroutine1
helloworld!

这是对的

接下来我们改进一下,改成两个goroutine同时运行

功能:

第一个goroutine往message写入字符串,

另一个goroutine取出字符串,

并将字符串和另一个字符串进行拼接,再写入message通道中,

然后在main主函数中取出message通道中的拼接过的字符串,输出打印出来。

代码实现:


package main

import (
"fmt"
   "time"
)

func main()  {
//创建通道
var message = make(chan string)
go func() { //第一个goroutine往通道中写入
message <-"hello goroutine"
}()
go func() {
str:=<-message//第二个goroutine从通道中取出字符串
str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串
message<-str //将新的字符串重新写入到message通道中
}()
time.Sleep(3*time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接returnfmt.Println(<-message) //通道中取字符串并输出打印
fmt.Println("helloworld!")
}

运行结果:

hello goroutine!,i am goroutine!
helloworld!

没有问题


以上是单通道版本,考虑一下能不能多个通道同时运行呢?答案是可以的。现在我们来演示一下

代码如下:


package main

import (
"fmt"
   "time"
)

func main()  {
//创建通道
//goroutine的原则是FIFO,first in first out,先进先出,所以第一个message会先写入到通道message//然后进行字符串拼接,拼接后直接main函数中打印输出了,当goroutine1输出后,其它goroutine就没机会拼接字符串了
//所以他们输出的内容就是无拼接的原始内容
/*
      hello goroutine2
      hello goroutine3
      hello goroutine1,i am goroutine!
      helloworld!
输出结果是对的
   */
var message = make(chan string,3) //通道类型后面设置要开几条通道的数量,整数的
go func() { //第一个goroutine往通道中写入
message <-"hello goroutine1"
message <-"hello goroutine2"
message <-"hello goroutine3"
}()
go func() {
str:=<-message//第二个goroutine从通道中取出字符串
str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串
message<-str //将新的字符串重新写入到message通道中
}()
time.Sleep(3*time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接return//通道中取字符串并输出打印,因为用了多通道,所以上面通道的数量设置为几个。这里就取几次
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println("helloworld!")
}

现在有个设想,如果设置通道数量为3.但是往通道中写入4条数据的话会发生什么事呢?

我们来演示一下:


package main

import (
"fmt"
   "time"
)

func main()  {
//创建通道
//goroutine的原则是FIFO,first in first out,先进先出,所以第一个message会先写入到通道message//然后进行字符串拼接,拼接后直接main函数中打印输出了,当goroutine1输出后,其它goroutine就没机会拼接字符串了
//所以他们输出的内容就是无拼接的原始内容
/*
      hello goroutine2
      hello goroutine3
      hello goroutine1,i am goroutine!
      helloworld!
输出结果是对的
   */
var message = make(chan string,3) //通道类型后面设置要开几条通道的数量,整数的
go func() { //第一个goroutine往通道中写入
message <-"hello goroutine1"
message <-"hello goroutine2"
message <-"hello goroutine3"
message <-"hello goroutine4"  //通道数量为3,但是往通道中写入了4条数据
go func() {
str:=<-message//第二个goroutine从通道中取出字符串
str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串
message<-str //将新的字符串重新写入到message通道中
}()
time.Sleep(3*time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接return//通道中取字符串并输出打印,因为用了多通道,所以上面通道的数量设置为几个。这里就取几次
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println("helloworld!")
}

运行结果:

hello goroutine2
      hello goroutine3
      hello goroutine4
      helloworld!


运行结果发现goroutine1没有了呢。
原因是这样的:因为是先进先出原则,
刚开始通道数量为3 {[] [] []} 应该是这样的
然后往通道中写入3条数据是这样的 {[hello goroutine1] [hello goroutine2] [hello goroutine3]}
因为goroutine1是先进来的,理应先出,1输出后,2的位置就往前移到了1的位置上1就移到3的后面了,因为1已经空了,所以4就写到1里了
因为最后输出的只有3个通道,所以
输出的是{[hello goroutine2] [hello goroutine3] [hello goroutine4]}
这是对的,如果goroutine2再输出出去了,这样的话1才有机会进到2的位置上写入。

所以再main主函数message接收输出数据那里再加一条<-message就可以解决了。最好是声明的通道数量改成4

attachments-2018-05-AcelPVbj5b0eaab806ff0.

输出结果:

hello goroutine!2
hello goroutine!3
hello goroutine!4
hello goroutine!1,i am goroutine!
helloworld!

这是对的。


现在我想尝试将两个goroutine分别放到两个函数中去执行,提高一下执行效率

代码如下:


package main

import (
"fmt"
   "time"
)
//创建通道,这里要注意,因为函数中用到了message变量,所以需要把message变量设为全局变量
var message = make(chan string,4) //通道类型后面设置要开几条通道的数量,整数的
func sample()  {
message <-"hello goroutine1"
message <-"hello goroutine2"
message <-"hello goroutine3"
message <-"hello goroutine4"  //通道数量为3,但是往通道中写入了4条数据
}
func sample2()  {
str:=<-message//第二个goroutine从通道中取出字符串
str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串
message<-str //将新的字符串重新写入到message通道中
}
func main()  {
go sample()
go sample2()
time.Sleep(3*time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接return//通道中取字符串并输出打印,因为用了多通道,所以上面通道的数量设置为几个。这里就取几次
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println("helloworld!")
}

以下的例子其实就是把main主函数中的两个goroutine放到两个函数中分别运行了

其实道理是一样的。只不过这样看起来更规范,简洁一些。方便管理。以后我会将代码都会用函数的形式封装起来。

hello goroutine!2
hello goroutine!3
hello goroutine!4
hello goroutine!1,i am goroutine!
helloworld!
运行结果是一样的。没毛病

接下来我们还可以通过参数传递的方式将通道用参数传递的方式来传递数据

接着看下面代码

package main

import (
"fmt"
   "time"
)

func sample(message chan string)  { //形参为chan string类型
message <-"hello goroutine1"
  message <-"hello goroutine2"
message <-"hello goroutine3"
message <-"hello goroutine4"  //通道数量为3,但是往通道中写入了4条数据
}
func sample2(message chan string)  {//形参为chan string类型
str:=<-message//第二个goroutine从通道中取出字符串
str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串
message<-str //将新的字符串重新写入到message通道中
}
func main()  {
//创建通道,用到参数传递方式,最好还是放在和goroutine一起
var message = make(chan string,4) //通道类型后面设置要开几条通道的数量,整数的
go sample(message) //将通道传递进去,函数再设置形参
go sample2(message)//将通道传递进去,函数再设置形参
time.Sleep(3*time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接return//通道中取字符串并输出打印,因为用了多通道,所以上面通道的数量设置为几个。这里就取几次
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println(<-message)
fmt.Println("helloworld!")
}

上面主要修改的地方就是形参和实参,什么是形参呢。函数后面的参数就叫形参,函数被调用的时候的参数就叫实参

上面代码改动三处地方

1处,把声明的通道还是放在main函数中比较好

然后main函数中的go sample(message)传实参到函数中,go sample2(message)也传实参到函数中

然后把func sample(message chan string){} sample函数中传入形参,同时sample2函数中也传入形参message chan string

运行结果:

hello goroutine!2
hello goroutine!3
hello goroutine!4
hello goroutine!1,i am goroutine!
helloworld!

没毛病。

好了。更细节的请自行百度,我只懂这些

猜你喜欢

转载自blog.csdn.net/xiaonu123/article/details/80557961