Go 中如何阻塞等待所有 goroutines 都完成


方案一:

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

// 定义一个同步等待的组
var wg sync.WaitGroup

// 定义一个Printer函数用于并发
func Printer(a int) {
    time.Sleep(2000 * time.Millisecond)
    fmt.Printf("i am %d\n", a)
    defer wg.Done()
}

func main() {
    // 获取cpu个数
    maxProcs := runtime.NumCPU()
    // 限制同时运行的goroutines数量
    runtime.GOMAXPROCS(maxProcs)
    for i := 0; i < 10; i++ {
        //为同步等待组增加一个成员
        wg.Add(1)
        //并发一个goroutine
        go Printer(i)
    }
    // 阻塞等待所有组内成员都执行完毕退栈
    wg.Wait()
    fmt.Println("WE DONE!!!")
}

阅读全文

Go 处理 HTTP 请求


引入包

import (
    "net/http"
)

具体实现

// HTTPGet compatible http & https
func HTTPGet(reqURL string) []byte {
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    c := &http.Client{
        Transport: tr,
        Timeout:   30 * time.Second,
    }
    res, err := c.Get(reqURL)
    if err != nil {
        fmt.Println(err)
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}

// HTTPPost is post func
func HTTPPost(reqURL, reqData string) map[string]interface{} {
    req, _ := http.NewRequest("POST", reqURL, strings.NewReader(reqData))
    req.Header.Set("Content-Type", "application/json")
    c := &http.Client{
        Timeout: 9 * time.Second,
    }
    res, err := c.Do(req)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}

// HTTPPut is post func
func HTTPPut(reqURL, reqData string) map[string]interface{} {
    req, _ := http.NewRequest("PUT", reqURL, strings.NewReader(reqData))
    req.Header.Set("Content-Type", "application/json")
    c := &http.Client{
        Timeout: 9 * time.Second,
    }
    res, err := c.Do(req)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}

// HTTPDelete is delete func
func HTTPDelete(reqURL, reqData string) map[string]interface{} {
    req, _ := http.NewRequest("DELETE", reqURL, strings.NewReader(reqData))
    req.Header.Set("Content-Type", "application/json")
    c := &http.Client{
        Timeout: 9 * time.Second,
    }
    res, err := c.Do(req)
    if err != nil {
        fmt.Println(err)
        return nil
    }
    body, _ := ioutil.ReadAll(res.Body)
    res.Body.Close()
    responeDate := make(map[string]interface{})
    json.Unmarshal([]byte(string(body)), &responeDate)
    return responeDate
}
阅读全文

Go 语言小细节(一)


多个 defer 出现的时候,多个 defer 之间按照 LIFO(后进先出)的顺序执行

package main
import "fmt"
func main(){
    defer func(){
        fmt.Println("1")
    }()
    defer func(){
        fmt.Println("2")
    }()
    defer func(){
        fmt.Println("3")
    }()
}

对应的输出是:

3
2
1

阅读全文

Go 竞争检测


工作原理:
竞争检测器集成在 Go 工具链中。当使用了 -race 作为命令行参数后,编译器会插桩代码,使得所有代码在访问内存时,会记录访问时间和方法。同时运行时库会观察对共享变量的未同步访问。当这种竞争行为被检测到,就会输出一个警告信息。由于设计原因,竞争检测器只有在被运行的代码触发时,才能检测到竞争条件,因此在现实的负载条件下运行是非常重要的。但是由于代码插桩,程序会使用10倍的 CPU 和内存,所以总是运行插桩后的程序是不现实的。矛盾的解决方法之一就是使用插桩后的程序来运行测试。负载测试和集成测试是好的候选,因为它们倾向于检验代码的并发部分。另外的方法是将单个插桩后的程序布置到运行服务器组成的池中,并且给予生产环境的负载。

// 测试包
go test -race mypkg
// 编译和运行程序
go run -race mysrc.go
// 构建程序
go build -race mycmd
// 安装程序
go install -race mypkg
阅读全文

Go Redis 库 Redigo


https://github.com/garyburd/redigo/

引入库

import (
    "github.com/garyburd/redigo/redis"
)

查询数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
// 查询 key
val, err := redis.String(cli.Do("GET", key))
if err != nil {
    fmt.Println(err)
}
return val

写入数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
_, err := cli.Do("SET", key, value)
if err != nil {
    fmt.Println(err)
}
阅读全文

Go 单元测试


Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试。
实际操作就是建立一个 gotest 目录。在该目录下面创建两个文件:gotest.go 和 gotest_test.go。
gotest.go 这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:

package gotest

import (
    "errors"
)
// 除法函数
func Division(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

gotest_test.go 这是我们的单元测试文件,但是记住下面的这些原则:

  • 文件名必须是 _test.go 结尾的,这样在执行 go test 的时候才会执行到相应的代码。
  • 你必须 import testing 这个包。
  • 所有的测试用例函数必须是 Test 开头。
  • 测试用例会按照源代码中写的顺序依次执行。
  • 测试函数 TestXxx() 的参数是 testing.T,我们可以使用该类型来记录错误或者是测试状态。

测试格式:
func TestXxx (t *testing.T),Xxx 部分可以为任意的字母数字的组合,但是首字母不能是小写字母 [a-z],例如
Testintdiv 是错误的函数名。函数中通过调用 testing.T 的 Error,Errorf,FailNow,Fatal,FatalIf 方法,说明测试不通过,调用 Log 方法用来记录测试的信息。





阅读全文

Go make 和 new 的区别


new 和 make 都可以用来分配空间,初始化类型,但是它们确有不同。
new(T)返回的是 T 的指针
new(T)为一个 T 类型新值分配空间并将此空间初始化为 T 的零值,返回的是新值的地址,也就是 T 类型的指针 *T,该指针指向 T 的新分配的零值。

package main

import "fmt"

func main() {
    p1 := new(int)
    fmt.Printf("p1 --> %#v \n ", p1)
    fmt.Printf("p1 point to --> %#v \n ", *p1)

    var p2 *int
    i := 0
    p2 = &i
    fmt.Printf("p2 --> %#v \n ", p2)
    fmt.Printf("p2 point to --> %#v \n ", *p2)
}


阅读全文

Go 语言小细节(二)


数组用于函数传参时是值复制
方法或函数调用时,传入参数都是值复制,跟赋值一致,除非是 map、slice、channel、指针类型这些特殊类型是引用传递。

x := [3]int{1,2,3}

数组在函数中传参是值复制

func(arr [3]int) {
    arr[0] = 7
    fmt.Println(arr)
    // [7 2 3]
}(x)
fmt.Println(x)
// [1 2 3]

使用数组指针实现引用传参

func(arr *[3]int) {
    (*arr)[0] = 7
    fmt.Println(arr)
    // &[7 2 3]
}(&x)
fmt.Println(x)
// [7 2 3]

阅读全文