【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 關閉\n", address)
			continue
		}
		conn.Close()
		fmt.Printf(" %s 打開\n", address)
	}

	elasped := time.Since(start) / 1e9
	fmt.Printf("\n 經過了%d秒 \n", 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 關閉\n", address)
				return
			}
			conn.Close()
			fmt.Printf(" %s 打開\n", 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 關閉\n", address)
				return
			}
			conn.Close()
			fmt.Printf(" %s 打開\n", address)
		}(i)
	}
	wg.Wait()
	elasped := time.Since(start) / 1e9
	fmt.Printf("\n 經過了%d秒 \n", 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 關閉\n", address)
			continue
		}
		conn.Close()
		// fmt.Printf(" %s 打開\n", 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章