我有一个程序来检查关键字是否在网页上。但在检查1000-3000网址后,它会挂起。没有输出,它没有退出,并且tcp连接的数量为零。我不知道为什么没有新的联系。
你能给我一些如何调试它的建议吗?
type requestReturn struct {
url string
status bool
}
var timeout = time.Duration(800 * time.Millisecond)
func checkUrls(urls []string, kws string, threadLimit int) []string {
limitChan := make(chan int, threadLimit)
ok := make(chan requestReturn, 1)
var result []string
i := 0
for ; i < threadLimit; i++ {
go func(u string) {
request(u, limitChan, ok, kws)
}(urls[i])
}
for o := range ok {
if o.status {
result = append(result, o.url)
log.Printf("success %s,remain %d", o.url, len(urls)-i)
} else {
log.Printf("fail %s,remain %d", o.url, len(urls)-i)
}
if i < len(urls) {
go func(u string) {
request(u, limitChan, ok, kws)
}(urls[i])
i++
}
}
close(limitChan)
return result
}
func dialTimeout(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, timeout)
}
func request(url string, threadLimit chan int, ok chan requestReturn, kws string) {
threadLimit <- 1
log.Printf("%s, start...", url)
//startTime := time.Now().UnixNano()
rr := requestReturn{url: url}
transport := http.Transport{
Dial: dialTimeout,
DisableKeepAlives: true,
}
client := http.Client{
Transport: &transport,
Timeout: time.Duration(15 * time.Second),
}
resp, e := client.Get(url)
if e != nil {
log.Printf("%q", e)
rr.status = false
return
}
if resp.StatusCode == 200 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("%q", err)
rr.status = false
return
}
content := bytes.NewBuffer(body).String()
matched, err1 := regexp.MatchString(kws, content)
if err1 != nil {
log.Printf("%q", err1)
rr.status = false
} else if matched {
rr.status = true
log.Println(rr.url)
} else {
rr.status = false
}
} else {
rr.status = false
}
defer (func() {
resp.Body.Close()
ok <- rr
//processed := float32(time.Now().UnixNano()-startTime) / 1e9
//log.Printf("%s, status:%t,time:%.3fs", rr.url, rr.status, processed)
<-threadLimit
})()
}
答案 0 :(得分:3)
您似乎在此代码中使用了两种形式的并发控制,并且都存在问题。
您已获得limitChan
,看起来它被用作信号量(request
在其开头发送一个值,并在defer
中收到一个值那个功能)。但checkUrls
也试图确保它只有threadLimit
goroutines同时运行(首先产生该数字,并且只有在ok
频道上报告其结果时才产生更多数量) 。只需要其中一个来限制并发性。
由于在defer
中设置request
的方式,这两种方法都失败了。在return
之前发生了许多defer
语句,因此可以在不将结果发送到ok
频道的情况下完成该功能,并且不会在limitChan
中释放其插槽{1}}。在出现足够数量的错误后,checkUrls
将停止生成新的goroutines并且您将看到您的挂起。
修复是将defer
语句放在任何return
语句之前,以便您知道它将始终运行。像这样:
func request(url string, threadLimit chan int, ok chan requestReturn, kws string) {
threadLimit <- 1
rr := requestReturn{url: url}
var resp *http.Response
defer func() {
if resp != nil {
resp.Body.Close()
}
ok <- rr
<-threadLimit
}()
...
}