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
,如果數值更大,100w
,1個億
(當然IP
端口數字沒這麼大),只是做大膽試驗:然後就可以在監控內存的懸浮窗看到內存噌噌噌
的往上漲,內存迅速消耗殆盡,直至系統卡死,甚至藍屏。
爲了避免這種情況,我們急需創建類似於線程池的機制,去限定我們創建goroutine
的數量,並複用。創建固定數量的m
個goroutine
,利用channel
的機制,往channel
中傳遞n
個數據,然後分配給這m
個goroutine
,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