1. 一个已经被关闭的 channel 永远都不会阻塞。当一个 channel 一旦被关闭,就不能再向这个 channel 发送数据,但仍然可以尝试从 channel 中获取值。
2. 已经被关闭的 channel 会实时返回。
package main import ( "fmt" "sync" "time" ) func main() { const n = 100000 finish := make(chan bool) var done sync.WaitGroup for i := 0; i < n; i++ { done.Add(1) go func() { select { case <-time.After(1 * time.Hour): case <-finish: } done.Done() }() } t0 := time.Now() close(finish) done.Wait() fmt.Printf("waited %v for %d goroutines to stop\n", time.Since(t0), n) }
当 finish channel 被关闭后,它会立刻返回。那么所有等待接收 time.After channel 或 finish 的 goroutine 的 select 语句就立刻完成了,并且 goroutine 在调用 done.Done() 来减少 WaitGroup 计数器后退出。这个强大的机制在无需知道未知数量的 goroutine 的任何细节而向它们发送信号而成为可能,同时也不用担心死锁。
当 close(finish) 依赖于关闭 channel 的消息机制,而没有数据收发时,将 finish 定义为 type chan struct{} 表示 channel 没有任何数据;只对其关闭的特性感兴趣。即:finish := make(chan struct{})
3. 当 channel 的值尚未进行初始化或赋值为 nil 时,永远都是阻塞的。
WaitMany() 中,一旦接收到一个值,就将 a 或 b 的引用设置为 nil。当 nil channel 是 select 语句的一部分时,它实际上会被忽略,因此,将 a 设置为 nil 便会将其从 select 中移除,仅仅留下 b 等待它被关闭,进而退出循环。
4. slice append函数的各种技巧
//添加切片 a = append(a, b, c, d) //将切片b添加至切片a a=append(a, b...) //复制切片 b := make([]int, len(a)) copy(b, a) //删除指定位置元素[i:j] a = append(a[:i], a[j:]...) //删除第n个元素 a = append(a[:n], a[n+1:]...) //扩展n个空元素 a = append(a, make([]int, n)...) //在第i个位置插入j个空元素 a = append(a[:i], append(make([]int, j), a[i:]...)...) //在第i个位置插入元素x a = append(a[:i], append([]int{x}, a[i:]...)...) //在i个位置插入切片 a = append(a[:i], append([]int{x, y}, a[i:]...)...)
其中删除操作就是覆盖;而插入操作需要注意不能覆盖掉插入位置及以后的元素。
5. 关于string与[]byte、[]rune相互转换的问题:
str := "hello世界" sli := []rune(str) []rune 是go内建的函数,会将字符串按utf8编码转换为{h,e,l,l,o,世,界}对应的数字{104,101,108,108,111,19990,30028} byt := []byte(str) []byte 是go内建函数,会将str转换为byte切片{104,101,108,108,111,228,184,150,231,149,140} for _,c := range str{ println(c) } len(str) 返回 11 len返回字符串byte长度 go 中的字符可以是 ASCII/中文 .. s := '你' string(sli)/string(byt) 都返回 "hello世界" string()是go内置函数 无论是[]rune或者[]byte 都能通过string()函数返回相应的字符串6. 在使用多个 goroutine 打印内容时,经常因为使用 chan 不恰当而 导致主线程未等待其它 goroutine 全部执行完毕而匆匆推出,造成打印内容不全的问题,这里对其中一种情况进行讲解。
package main import ( "fmt" "runtime" ) // 从 1 至 1 亿循环叠加,并打印结果。 func print(c chan bool, n int) { x := 0 for i := 1; i <= 100000000; i++ { x += i } fmt.Println(n, x) if n == 9 { c <- true } } func main() { // 使用多核运行程序 runtime.GOMAXPROCS(runtime.NumCPU()) c := make(chan bool) for i := 0; i < 10; i++ { go print(c, i) } <-c fmt.Println("DONE.") }这段代码从逻辑上看合乎情理,但是是一种非常 投机取巧 的做法,即根据第 10 个 goroutine 的执行情况来 草率地 认为前面的 9 个 goroutine 都已经执行完毕。如果你将 `runtime.GOMAXPROCS(runtime.NumCPU())` 这句注释掉,使用单核运行程序,则将得到你所预期的效果;但如果使用多核的情况下,这种做法就是 错误的。 goroutine 是相互独立的,且在执行过程中可能由于各种原因导致其中几个 goroutine 让出时间片给 CPU 去执行其它 goroutine。所以,我们 不能够依靠 第 10 个 goroutine 的执行结果来判断程序的运行情况。
解决方案一:利用 chan 的缓存机制
package main import ( "fmt" "runtime" ) // 从 1 至 1 亿循环叠加,并打印结果。 func print(c chan bool, n int) { x := 0 for i := 1; i <= 100000000; i++ { x += i } fmt.Println(n, x) c <- true } func main() { // 使用多核运行程序 runtime.GOMAXPROCS(runtime.NumCPU()) c := make(chan bool, 10) for i := 0; i < 10; i++ { go print(c, i) } for i := 0; i < 10; i++ { <-c } fmt.Println("DONE.") }解决方案二: 使用 sync 包的 WaitGroup
package main import ( "fmt" "runtime" "sync" ) // 从 1 至 1 亿循环叠加,并打印结果。 func print(wg *sync.WaitGroup, n int) { x := 0 for i := 1; i <= 100000000; i++ { x += i } fmt.Println(n, x) // 标识一次任务完成 wg.Done() } func main() { // 使用多核运行程序 runtime.GOMAXPROCS(runtime.NumCPU()) // 创建等待组 wg := sync.WaitGroup{} // 设置需要等待的对象个数 wg.Add(10) for i := 0; i < 10; i++ { go print(&wg, i) } // 等待所有任务完成 wg.Wait() fmt.Println("DONE.") }
7. golang http请求优化
//判断url是否有效 //没有http://开头,就加上http:// if !strings.HasPrefix(feed, "http") { feed = "http://" + feed } //判断url是否合理 host, err := url.ParseRequestURI(feed) if err != nil { } //判断是否能解析到对应的host记录 _, err = net.LookupIP(host.Host) if err != nil { } //向主机请求数据 client := &http.Client{ Transport: &http.Transport{ Dial: func(netw, addr string) (net.Conn, error) { deadline := time.Now().Add(10 * time.Second) c, err := net.DialTimeout(netw, addr, 5*time.Second) //连接超时时间 if err != nil { return nil, err } c.SetDeadline(deadline) return c, nil }, }, } req, err := http.NewRequest("GET", feed, nil) if err != nil { } //数据传输压缩 //告诉主机 支持gzip 数据请求回来后 ungzip req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; UJCspider/0.1; +http://ujiecao.com/help)") req.Header.Set("Accept-Encoding", "gzip") resp, err := client.Do(req) if err != nil { } defer resp.Body.Close() var reader io.ReadCloser switch resp.Header.Get("Content-Encoding") { case "gzip": reader, _ = gzip.NewReader(resp.Body) defer reader.Close() default: reader = resp.Body }