伴鱼面试题

1 用time.After和context实现,两个方法本质上都是用了context或chan进行传递信息,如果go协程结束了,则用cancel关闭协程或往channel中传入值,同时用case time.After进行阻塞,若go协程超时了,则会走case time.After通道,

两种方法,第一种是go协程里嵌套了go协程,第二种是先启动n个go协程去运行,再启动n个go协程去监控,

package main
import (
    "context"
    "fmt"
    "sync"
    "time"
)
//实现从三个网站爬取数据并用map保存的功能,如果协程超过1秒,则直接返回,
// 1 用time.After结合context实现,
// https://shockerli.net/post/golang-select-time-implement-timeout/
var res = make(map[string]string)
var rw sync.RWMutex
func writeMap(ctx context.Context, key string, cancel context.CancelFunc) {
    rw.Lock()
    res[key] = (" " + key + " 网站爬取结果")
    rw.Unlock()
    // 写这个是为了节省时间,因为有可能go协程的运行时间小于WithTimeout中所给的时间,
    // 导致程序已经取完数据了,go协程仍然没有结束,
    cancel()
}
// 这里必须要开启两个go协程,一个用于运行爬虫程序,另一个用于监控时间,其中一个case是time.After即超时的channel,
// 另一个是go协程结束后,会close ctx.Done,或者在规定的WithTimeout时间内go协程没有结束的话,WithTimeout内部会close ctx.Done
func doSomething(key string) {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second * 4)
    // 这个是为了防止忘记关闭协程,导致内存泄漏,
    defer cancel()
    go writeMap(ctx, key, cancel)
    select {
    case <- ctx.Done():
        fmt.Println(key + "网站爬取完毕!!!")
    // 注意这个time.After返回的是一个time类型的chan,所以这里可以这样写,
    case <-time.After(time.Second * 5):
        fmt.Println(key + "网站爬取超时!!!")
    }
}
func main() {
    var url = []string{
        "www.baidu.com",
        "www.123.com",
        "www.456.com",
    }
    for _, num := range url {
        go doSomething(num)
    }
    time.Sleep(time.Second * 8)
    fmt.Println(res)
}
View Code

用time.After和chan实现

// 只用time.After实现,这个方法的关键是当goroutine结束时,向chan通道中传入一个string,之后再用一个case去读取,
// 如果能读到,则说明没有超时,否则走超时的case,
var res sync.Map
var wg sync.WaitGroup
// 需要缓冲,不然阻塞
var sign = make(chan string, 3)
func writeMap(key string) {
    res.Store(key, key+" 网站爬虫结果")
}
func doSomething(key string) {
    writeMap(key)
    sign <- key
}
func doSomething1(key string) {
    for {
        writeMap(key)
    }
    sign <- key
}
func main() {
    var url = []string{
        "www.baidu.com",
        "www.123.com",
        "www.456.com",
    }
    for index, num := range url {
        if index == 2 {
            go doSomething1(num)
        } else {
            // 用死循环查看超时的情况,
            go doSomething(num)
        }
    }
    wg.Add(len(url))
    for _, num := range url {
        go func() {
            defer wg.Done()
            select {
            case r := <-sign:
                fmt.Println(r + "网站爬取完毕!!!")
                return
            // 注意这个time.After返回的是一个time类型的chan,所以这里可以这样写,
            case <-time.After(time.Second * 3):
                fmt.Println(num + "网站爬取超时!!!")
                return
            }
        }()
    }
    wg.Wait()
}
View Code

参考:https://shockerli.net/post/golang-select-time-implement-timeout/ 这是类似第二种方法,只不过只有一个goroutine,

原文地址:https://www.cnblogs.com/xxswkl/p/14255855.html