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


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

    1.循环扫描

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

    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 关闭
    ", address)
    			continue
    		}
    		conn.Close()
    		fmt.Printf(" %s 打开
    ", address)
    	}
    
    	elasped := time.Since(start) / 1e9
    	fmt.Printf("
     经过了%d秒 
    ", elasped)
    }
    
    
    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并发扫描

    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 关闭
    ", address)
    				return
    			}
    			conn.Close()
    			fmt.Printf(" %s 打开
    ", address)
    		}(i)
    	}
    	wg.Wait()
    	elasped := time.Since(start) / 1e9
    	fmt.Printf("经过了%d秒", elasped)
    }
    
    
    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

    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 关闭
    ", address)
    				return
    			}
    			conn.Close()
    			fmt.Printf(" %s 打开
    ", address)
    		}(i)
    	}
    	wg.Wait()
    	elasped := time.Since(start) / 1e9
    	fmt.Printf("
     经过了%d秒 
    ", elasped)
    }
    
    
     ...
     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。

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

    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 关闭
    ", address)
    			continue
    		}
    		conn.Close()
    		// fmt.Printf(" %s 打开
    ", address)
    		results <- port
    	}
    }
    
    
    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
    

    作者:Garfield

    同步更新至个人博客:http://www.randyfield.cn/

    本文版权归作者所有,未经许可禁止转载,否则保留追究法律责任的权利,若有需要请联系287572291@qq.com

  • 相关阅读:
    Android网络开发的那些事儿
    first day to Ruby on rails
    [转]Windows SDK与DirectX SDK集成
    Windows8
    [转]MPI--MPI+VS2010 配置及编译
    codeblock添加头文件路径和静态库路径
    汇编笔记1:debug
    Eclipse Error
    Android SDk 离线安装方法
    求一程序员合租,回龙观东大街地铁站十分钟,精装次卧2000,无需押金,一共两家
  • 原文地址:https://www.cnblogs.com/RandyField/p/14134549.html
Copyright © 2020-2023  润新知