前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Golang】快速复习指南QuickReview(十)——goroutine池

【Golang】快速复习指南QuickReview(十)——goroutine池

作者头像
DDGarfield
发布2022-06-23 19:11:54
2030
发布2022-06-23 19:11:54
举报
文章被收录于专栏:加菲的博客加菲的博客

goroutine的栈在其生命周期开始时很小,可能只有2KB,但是它并不固定,可按需增大或减小。虽然我们可以无脑创建很多goroutine来执行操作,但是如果程序出现意外,goroutine可能会暴涨占据内存,一切就变得不可控,比如我们通过循环来创建goroutine,当循环条件满足,创建巨额的goroutine,严重时系统会崩溃。博主也是通过杨旭老师的TCP端口扫描器中发现了这个问题。

1.循环扫描

不使用goroutine,直接循环扫描端口。

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
 "time"
)

func main() {
 fmt.Println("TCP端口扫描启动...")
 start := time.Now()
 for i := 1; i <= 20; i++ {
  address := fmt.Sprintf("192.168.0.109:%d", i)
  conn, err := net.Dial("tcp", address)
  if err != nil {
   fmt.Printf(" %s 关闭\n", address)
   continue
  }
  conn.Close()
  fmt.Printf(" %s 打开\n", address)
 }

 elasped := time.Since(start) / 1e9
 fmt.Printf("\n 经过了%d秒 \n", elasped)
}

代码语言:javascript
复制
TCP端口扫描启动...
 192.168.0.109:1 关闭
 192.168.0.109:2 关闭
 192.168.0.109:3 关闭
 192.168.0.109:4 关闭
 192.168.0.109:5 关闭
 192.168.0.109:6 关闭
 192.168.0.109:7 关闭
 192.168.0.109:8 关闭
 192.168.0.109:9 关闭
 192.168.0.109:10 关闭
 192.168.0.109:11 关闭
 192.168.0.109:12 关闭
 192.168.0.109:13 关闭
 192.168.0.109:14 关闭
 192.168.0.109:15 关闭
 192.168.0.109:16 关闭
 192.168.0.109:17 关闭
 192.168.0.109:18 关闭
 192.168.0.109:19 关闭
 192.168.0.109:20 关闭

 经过了40秒

2.并发扫描

利用goroutine并发扫描

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
 "sync"
 "time"
)

var wg sync.WaitGroup

func main() {
 fmt.Println("TCP端口扫描启动...")
 start := time.Now()
 for i := 1; i <= 20; i++ {
  wg.Add(1)
  go func(i int) {
   defer wg.Done()
   address := fmt.Sprintf("192.168.0.109:%d", i)
   conn, err := net.Dial("tcp", address)
   if err != nil {
    fmt.Printf(" %s 关闭\n", address)
    return
   }
   conn.Close()
   fmt.Printf(" %s 打开\n", address)
  }(i)
 }
 wg.Wait()
 elasped := time.Since(start) / 1e9
 fmt.Printf("经过了%d秒", elasped)
}

代码语言:javascript
复制
TCP端口扫描启动...
 192.168.0.109:4 关闭
 192.168.0.109:6 关闭 
 192.168.0.109:11 关闭
 192.168.0.109:7 关闭 
 192.168.0.109:8 关闭 
 192.168.0.109:14 关闭
 192.168.0.109:3 关闭
 192.168.0.109:20 关闭
 192.168.0.109:16 关闭
 192.168.0.109:12 关闭
 192.168.0.109:1 关闭
 192.168.0.109:17 关闭
 192.168.0.109:18 关闭
 192.168.0.109:13 关闭
 192.168.0.109:9 关闭
 192.168.0.109:10 关闭
 192.168.0.109:5 关闭
 192.168.0.109:15 关闭
 192.168.0.109:19 关闭
 192.168.0.109:2 关闭
经过了2秒

即使代码改成全端口1~65535,时间也只有21s

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
 "sync"
 "time"
)

var wg sync.WaitGroup

func main() {
 fmt.Println("TCP端口扫描启动...")
 start := time.Now()
 for i := 1; i <= 65535; i++ {
  wg.Add(1)
  go func(i int) {
   defer wg.Done()
   address := fmt.Sprintf("192.168.0.109:%d", i)
   conn, err := net.Dial("tcp", address)
   if err != nil {
    fmt.Printf(" %s 关闭\n", address)
    return
   }
   conn.Close()
   fmt.Printf(" %s 打开\n", address)
  }(i)
 }
 wg.Wait()
 elasped := time.Since(start) / 1e9
 fmt.Printf("\n 经过了%d秒 \n", elasped)
}

代码语言:javascript
复制
 ...
 192.168.0.109:39702 关闭
 192.168.0.109:37282 关闭
 192.168.0.109:39490 关闭
 192.168.0.109:39795 关闭
 192.168.0.109:39613 关闭
 192.168.0.109:39707 关闭
 192.168.0.109:39723 关闭
 192.168.0.109:39732 关闭
 192.168.0.109:39806 关闭
 192.168.0.109:39682 关闭
 192.168.0.109:39809 关闭
 192.168.0.109:39663 关闭
 192.168.0.109:40006 关闭
 192.168.0.109:39712 关闭
 192.168.0.109:39804 关闭
 192.168.0.109:39731 关闭
 192.168.0.109:39701 关闭
 192.168.0.109:39810 关闭
 192.168.0.109:39725 关闭
 192.168.0.109:39805 关闭
 192.168.0.109:39713 关闭
 192.168.0.109:39489 关闭
 192.168.0.109:39791 关闭
 192.168.0.109:39640 关闭
 192.168.0.109:39667 关闭
 192.168.0.109:39661 关闭

 经过了21秒

这里也可以看出使用goroutine做并发操作,真的很快。

3.goroutine pool(池)

上一节最后这样做,就创建了6w多次goroutine,如果数值更大,100w1个亿(当然IP端口数字没这么大),只是做大胆试验:然后就可以在监控内存的悬浮窗看到内存噌噌噌的往上涨,内存迅速消耗殆尽,直至系统卡死,甚至蓝屏。

为了避免这种情况,我们急需创建类似于线程池的机制,去限定我们创建goroutine的数量,并复用。创建固定数量的mgoroutine,利用channel的机制,往channel中传递n个数据,然后分配给这mgoroutine,m<=n。

同样是端口扫描任务,代码改造如下:

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
)

func main() {
 fmt.Println("TCP端口扫描启动...")
 fmt.Println("下列端口状态为打开:")
 n := make(chan int, 100)
 results := make(chan int, 100)
    
    //创建20000个goroutine
 for m := 0; m < 20000; m++ {
  go worker(n, results)
 }
    
    //channel代表了任务数量
 for i := 1; i < 65535; i++ {
  n <- i
 }
    close(n)
    
    //结果
 for port := range results {
  fmt.Println(port)
 }

}

func worker(ports <-chan int, results chan<- int) {
 for port := range ports {
  address := fmt.Sprintf("192.168.0.109:%d", port)
  conn, err := net.Dial("tcp", address)
  if err != nil {
   // fmt.Printf(" %s 关闭\n", address)
   continue
  }
  conn.Close()
  // fmt.Printf(" %s 打开\n", address)
  results <- port
 }
}

代码语言:javascript
复制
TCP端口扫描启动...
下列端口状态为打开:
80
81   
139  
902  
443  
445  
135  
912  
1433 
2383 
2179 
3306 
5357 
5040 
7680 
8080 
33060
43094
43095
49672
49664
49667
49666
49670
49665
50272
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 加菲的博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.循环扫描
  • 2.并发扫描
  • 3.goroutine pool(池)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档