go channel的一些技巧

壬癸甲乙 · · 6633 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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
}



有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:壬癸甲乙

查看原文:go channel的一些技巧

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

6633 次点击  ∙  1 赞  
加入收藏 微博
1 回复  |  直到 2000-01-01 00:00:00
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传