工作,学习,生活,这里将会有一些记录. 备用域名:http://meisw.wdlinux.cn 注册 | 登陆
浏览模式: 标准 | 列表分类:golang

golang 生成RSA公钥和私钥文件

 package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"

)

func main() {
//rsa 密钥文件产生
GenRsaKey(1024)
}
//RSA公钥私钥产生
func GenRsaKey(bits int) error {
// 生成私钥文件
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
derStream := x509.MarshalPKCS1PrivateKey(privateKey)
block := &pem.Block{
Type:  "RSA PRIVATE KEY",
Bytes: derStream,
}
file, err := os.Create("private.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
// 生成公钥文件
publicKey := &privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return err
}
block = &pem.Block{
Type:  "PUBLIC KEY",
Bytes: derPkix,
}
file, err = os.Create("public.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
return nil
}

在Go语言中使用JSON

 注意,这里 json的 struct field's tag 规范定义如下:

在Go语言里,StructTag是一个标记字符串,此字符串可跟随在Struct中字段定义的后面。

StructTag就是一系列的 key:”value” 形式的组合,其中key是一个不可为空的字符串,key-value组合可以有多个,空格分隔。

StructTag有什么用?!StructTag主要解决了不同类型数据集合间(Struct,Json,Table等)转换中键值Key定义不一样的问题。StructTag可以理解为一个不同数据类型键值Key的映射表Map, 在StructTag中可以定义不同数据集合键值和Struct中Key值的映射关系,这样方便了Struct数据转为其他类型数据的过程。

在StructTag中加入”omitempty”, 标识该字段的数据可忽略。

- 指定到一个field时,无论有没有值将Person序列化成json时都会忽略该字段 

参考下面代码, 代码来自:http://studygolang.com/articles/1698

 

//tag中的第一个参数是用来指定别名 
//比如Name 指定别名为 username `json:"username"` 
//如果不想指定别名但是想指定其他参数用逗号来分隔 
//omitempty 指定到一个field时 如果在赋值时对该属性赋值 或者 对该属性赋值为 zero value 
//那么将Person序列化成json时会忽略该字段 
//- 指定到一个field时 
//无论有没有值将Person序列化成json时都会忽略该字段 
//string 指定到一个field时 
//比如Person中的Count为int类型 如果没有任何指定在序列化 
//到json之后也是int 比如这个样子 "Count":0 
//但是如果指定了string之后序列化之后也是string类型的 
//那么就是这个样子"Count":"0" 

http://www.cnblogs.com/ghj1976/p/4281793.html


---------------

Encode

将一个对象编码成JSON数据,接受一个interface{}对象,返回[]byte和error:

func Marshal(v interface{}) ([]byte, error)

Marshal函数将会递归遍历整个对象,依次按成员类型对这个对象进行编码,类型转换规则如下:

bool类型 转换为JSON的Boolean
整数,浮点数等数值类型 转换为JSON的Number
string 转换为JSON的字符串(带""引号)
struct 转换为JSON的Object,再根据各个成员的类型递归打包
数组或切片 转换为JSON的Array
[]byte 会先进行base64编码然后转换为JSON字符串
map 转换为JSON的Object,key必须是string
interface{} 按照内部的实际类型进行转换
nil 转为JSON的null
channel,func等类型 会返回UnsupportedTypeError

http://blog.csdn.net/tiaotiaoyly/article/details/38942311

go get 获得 golang.org 的项目

 go get 用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行go install。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下:

BitBucket (Mercurial Git) 
GitHub (Git) 
Google Code Project Hosting (Git, Mercurial, Subversion) 
Launchpad (Bazaar)

go get 的参数说明:

-d 只下载不安装 
-f 只有在你包含了-u参数的时候才有效,不让-u去验证import中的每一个都已经获取了,这对于本地fork的包特别有用 
-fix 在获取源码之后先运行fix,然后再去做其他的事情 
-t 同时也下载需要为运行测试所需要的包 
-u 强制使用网络去更新包和它的依赖包 
-v 显示执行的命令 
注意,这里的 –v 参数对我们分析问题很有帮助。

参考:https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/01.3.md

 

国内由于墙,我们会收到 unrecognized import path 的错误,这时候我们如何通过命令行来执行 go get 呢? 
这时我们会获得类似如下错误:

go get -u -v golang.org/x/oauth2 
Fetching https://golang.org/x/oauth2?go-get=1 
https fetch failed. 
import "golang.org/x/oauth2": https fetch: Get https://golang.org/x/oauth2?go-get=1: dial tcp 216.58.221.145:443: i/o timeout 
package golang.org/x/oauth2: unrecognized import path "golang.org/x/oauth2" 
localhost:~ ghj1976$

如果目录下有以前的版本,则是如下情况:

go get -u -v golang.org/x/oauth2 
Fetching https://golang.org/x/oauth2?go-get=1 
https fetch failed. 
import "golang.org/x/oauth2": https fetch: Get https://golang.org/x/oauth2?go-get=1: dial tcp 216.58.221.145:443: i/o timeout 
golang.org/x/oauth2 (download) 
Fetching https://golang.org/x/net/context?go-get=1 
https fetch failed. 
import "golang.org/x/net/context": https fetch: Get https://golang.org/x/net/context?go-get=1: dial tcp 216.58.221.145:443: i/o timeout 
golang.org/x/net (download) 
Fetching https://golang.org/x/oauth2/internal?go-get=1 
https fetch failed. 
import "golang.org/x/oauth2/internal": https fetch: Get https://golang.org/x/oauth2/internal?go-get=1: dial tcp 216.58.221.145:443: i/o timeout 
golang.org/x/net/context 
golang.org/x/oauth2/internal 
golang.org/x/oauth2 
localhost:~ ghj1976$

 

这时候我们需要设置代理。代理工具我推荐用 lantern https://github.com/getlantern/lantern

需要注意的是,它的代理地址是: http://127.0.0.1:8787   而不是 http://127.0.0.1:16823/ ,后一个是它的配置网站地址。

 

以mac为例, 在命令行 Terminal 中设置网络代理,一般方法如下:

root@ed27c545f7af:~# cat ~/proxy.conf  export http_proxy=http://172.17.42.1:8118 export https_proxy=$http_proxy export ftp_proxy=$http_proxy export rsync_proxy=$http_proxy export no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com"

参考:https://github.com/tools/godep/issues/154

win下 用 set 代理 export ,参考 https://groups.google.com/forum/#!topic/lantern-users-zh/FiywFrEHSHE 

删除环境变量用

删除:unset 变量名    参考 http://blog.csdn.net/debug_cpp/article/details/2679991 

当前系统上下文的环境设置可以用 env 命令查看。

image

https://code.google.com/p/go/issues/detail?id=2919

这步代理设置后,我们可以用 wget 命令去试验效果。参考: https://github.com/getlantern/lantern/issues/3341 

 

 

另外,go get 使用的 git 、mercurial、svn 设置代理的方法请参考:

https://github.com/golang/go/wiki/GoGetProxyConfig 

以我们最常用的 git 为例,

在终端设置: 
git config --global http.proxy http://127.0.0.1:1080 
git config --global https.proxy https://127.0.0.1:1080

默认不设置代理: 
git config --global --unset http.proxy 
git config --global --unset https.proxy

查看已经设置的值:

git config http.proxy

 

参考: http://blog.csdn.net/dengbin9009/article/details/38058153 

 

配置完成后,以下载 golang.org/x/net 为例,执行的返回值如下:

go get -u -v  golang.org/x/net 
Fetching https://golang.org/x/net?go-get=1 
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200) 
get "golang.org/x/net": found meta tag main.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} athttps://golang.org/x/net?go-get=1 
golang.org/x/net (download) 
package golang.org/x/net: no buildable Go source files in /Users/ghj1976/project/mygocode/src/golang.org/x/net 
localhost:text ghj1976$

 

我们可以看到其实是到 https://go.googlesource.com/text/ 这样的地址去下载源码的。中间涉及到跳转和git下载,所以 要注意, 网络请求的 http_proxy  和 git 的 代理 都需要设置才可以。

-----------------

命令行设置代理,确保可以下载的命令如下:

export http_proxy=http://127.0.0.1:8787 

git config --global http.proxy http://127.0.0.1:8787 
git config --global https.proxy https://127.0.0.1:8787

 

--------

go get -u golang.org/x/mobile

取消设置

unset http_proxy 
git config --global --unset http.proxy 
git config --global --unset https.proxy

------------

查看设置的变量

echo $http_proxy
git config http.proxy
git config https.proxy

go get使用代理

 git config –global http.proxy “127.0.0.1:8087”

go get …

或者可以在go get的同时指定代理:
http_proxy=127.0.0.1:8087 go get

------------------------------------

我的FQ方式(也可以使用别的方式):

  使用 ishadowsocks 方式FQ

 

临时设置Windows下代理:

  在控制台执行如下命令,后面的的代理值根据你具体的代理进行设置

set http_proxy=http://127.0.0.1:1080/pac?t=201603231602138322 set https_proxy=https://127.0.0.1:1080/pac?t=201603231602138322

 

临时设置Linux下代理:

  在控制台执行如下命令,后面的的代理值根据你具体的代理进行设置

http_proxy=http://127.0.0.1:1080/pac?t=201603231602138322 https_proxy=https://127.0.0.1:1080/pac?t=201603231602138322

 

此时,在控制台执行 go get 时即自动会通过代理。

golang exec交互执行

 package main

    import (     "bytes"     "fmt"     "os/exec" )     func main() {     in := bytes.NewBuffer(nil)     cmd := exec.Command("sh")     cmd.Stdin = in     go func() {         in.WriteString("echo hello world > test.txt\n")         in.WriteString("exit\n")     }()     if err := cmd.Run(); err != nil {         fmt.Println(err)         return     } }

Golang的select/非缓冲的Channel实例详解

 

select

golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。 
在执行select语句的时候,运行时系统会自上而下地判断每个case中的发送或接收操作是否可以被立即执行【立即执行:意思是当前Goroutine不会因此操作而被阻塞,还需要依据通道的具体特性(缓存或非缓存)】

  • 每个case语句里必须是一个IO操作
  • 所有channel表达式都会被求值、所有被发送的表达式都会被求值
  • 如果任意某个case可以进行,它就执行(其他被忽略)
  • 如果有多个case都可以运行,Select会随机公平地选出一个执行(其他不会执行)
  • 如果有default子句,case不满足条件时执行该语句。
  • 如果没有default字句,select将阻塞,直到某个case可以运行;Go不会重新对channel或值进行求值。

select 语句用法

注意到 select 的代码形式和 switch 非常相似, 不过 select 的 case 里的操作语句只能是【IO 操作】 。 
此示例里面 select 会一直等待等到某个 case 语句完成, 也就是等到成功从 ch1 或者 ch2 中读到数据,如果都不满足条件且存在default case, 那么default case会被执行。 则 select 语句结束。 
示例:

package main  import (     "fmt" )  func main(){     ch1 := make(chan int, 1)     ch2 := make(chan int, 1)      select {         case e1 := <-ch1:         //如果ch1通道成功读取数据,则执行该case处理语句             fmt.Printf("1th case is selected. e1=%v",e1)         case e2 := <-ch2:         //如果ch2通道成功读取数据,则执行该case处理语句             fmt.Printf("2th case is selected. e2=%v",e2)         default:         //如果上面case都没有成功,则进入default处理流程             fmt.Println("default!.")     } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

select分支选择规则

所有跟在case关键字右边的发送语句或接收语句中的通道表达式和元素表达式都会先被求值。无论它们所在的case是否有可能被选择都会这样。

求值顺序:自上而下、从左到右 
示例:

package main  import (     "fmt" ) //定义几个变量,其中chs和numbers分别代表了包含了有限元素的通道列表和整数列表 var ch1 chan int var ch2 chan int var chs = []chan int{ch1, ch2} var numbers = []int{1,2,3,4,5}  func main(){     select {         case getChan(0) <- getNumber(2):             fmt.Println("1th case is selected.")         case getChan(1) <- getNumber(3):             fmt.Println("2th case is selected.")         default:             fmt.Println("default!.")     } }  func getNumber(i int) int {     fmt.Printf("numbers[%d]\n", i)     return numbers[i] }  func getChan(i int) chan int {     fmt.Printf("chs[%d]\n", i)     return chs[i] }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

输出:

chs[0] 
numbers[2] 
chs[1] 
numbers[3] 
default!.

可以看出求值顺序。满足自上而下、自左而右这条规则。

随机执行case

如果同时有多个case满足条件,通过一个伪随机的算法决定哪一个case将会被执行。 
示例:

package main  import (     "fmt" ) func main(){     chanCap := 5     ch7 := make(chan int, chanCap)      for i := 0; i < chanCap; i++ {         select {             case ch7 <- 1:             case ch7 <- 2:             case ch7 <- 3:         }     }      for i := 0; i < chanCap; i++ {         fmt.Printf("%v\n", <-ch7)     } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

输出:(注:每次运行都会不一样)





1

一些惯用手法示例

示例一:单独启用一个Goroutine执行select,等待通道关闭后结束循环

package main  import (     "fmt"     "time" ) func main(){     //初始化通道     ch11 := make(chan int, 1000)     sign := make(chan int, 1)      //给ch11通道写入数据     for i := 0; i < 1000; i++ {         ch11 <- i     }     //关闭ch11通道     close(ch11)      //单独起一个Goroutine执行select     go func(){         var e int         ok := true          for{             select {                 case e,ok = <- ch11:                 if !ok {                     fmt.Println("End.")                     break                 }                 fmt.Printf("ch11 -> %d\n",e)             }              //通道关闭后退出for循环             if !ok {                 sign <- 0                 break             }         }      }()      //惯用手法,读取sign通道数据,为了等待select的Goroutine执行。     <- sign }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

ch11 -> 0 
ch11 -> 1 
… 
ch11 -> 999 
End.

示例二:加以改进,我们不想等到通道被关闭后再退出循环,利用一个辅助通道模拟出操作超时。

package main  import (     "fmt"     "time" ) func main(){     //初始化通道     ch11 := make(chan int, 1000)     sign := make(chan int, 1)      //给ch11通道写入数据     for i := 0; i < 1000; i++ {         ch11 <- i     }     //关闭ch11通道     close(ch11)      //我们不想等到通道被关闭之后再推出循环,我们创建并初始化一个辅助的通道,利用它模拟出操作超时行为     timeout := make(chan bool,1)     go func(){         time.Sleep(time.Millisecond) //休息1ms         timeout <- false     }()      //单独起一个Goroutine执行select     go func(){         var e int         ok := true          for{             select {                 case e,ok = <- ch11:                     if !ok {                         fmt.Println("End.")                         break                     }                     fmt.Printf("ch11 -> %d\n",e)                 case ok = <- timeout:                 //向timeout通道发送元素false后,该case几乎马上就会被执行, ok = false                     fmt.Println("Timeout.")                     break             }             //终止for循环             if !ok {                 sign <- 0                 break             }         }      }()      //惯用手法,读取sign通道数据,为了等待select的Goroutine执行。     <- sign }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

ch11 -> 0 
ch11 -> 1 
… 
ch11 -> 691 
Timeout.

示例三:上面实现了单个操作的超时,但是那个超时触发器开始计时有点早。

package main  import (     "fmt"     "time" ) func main(){     //初始化通道     ch11 := make(chan int, 1000)     sign := make(chan int, 1)      //给ch11通道写入数据     for i := 0; i < 1000; i++ {         ch11 <- i     }     //关闭ch11通道     //close(ch11),为了看效果先注释掉      //单独起一个Goroutine执行select     go func(){         var e int         ok := true          for{             select {                 case e,ok = <- ch11:                     if !ok {                         fmt.Println("End.")                         break                     }                     fmt.Printf("ch11 -> %d\n",e)                 case ok = <- func() chan bool {                     //经过大约1ms后,该接收语句会从timeout通道接收到一个新元素并赋值给ok,从而恰当地执行了针对单个操作的超时子流程,恰当地结束当前for循环                     timeout := make(chan bool,1)                     go func(){                         time.Sleep(time.Millisecond)//休息1ms                         timeout <- false                     }()                     return timeout                 }():                     fmt.Println("Timeout.")                     break             }             //终止for循环             if !ok {                 sign <- 0                 break             }         }      }()      //惯用手法,读取sign通道数据,为了等待select的Goroutine执行。     <- sign }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

ch11 -> 0 
ch11 -> 1 
… 
ch11 -> 999 
Timeout.

非缓冲的Channel

我们在初始化一个通道时将其容量设置成0,或者直接忽略对容量的设置,那么就称之为非缓冲通道

ch1 := make(chan int, 1) //缓冲通道 ch2 := make(chan int, 0) //非缓冲通道 ch3 := make(chan int) //非缓冲通道
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 向此类通道发送元素值的操作会被阻塞,直到至少有一个针对该通道的接收操作开始进行为止。
  • 从此类通道接收元素值的操作会被阻塞,直到至少有一个针对该通道的发送操作开始进行为止。
  • 针对非缓冲通道的接收操作会在与之相应的发送操作完成之前完成。

对于第三条要特别注意,发送操作在向非缓冲通道发送元素值的时候,会等待能够接收该元素值的那个接收操作。并且确保该元素值被成功接收,它才会真正的完成执行。而缓冲通道中,刚好相反,由于元素值的传递是异步的,所以发送操作在成功向通道发送元素值之后就会立即结束(它不会关心是否有接收操作)。

示例一

实现多个Goroutine之间的同步

package main  import (     "fmt"     "time" )  func main(){     unbufChan := make(chan int)     //unbufChan := make(chan int, 1) 有缓冲容量      //启用一个Goroutine接收元素值操作     go func(){         fmt.Println("Sleep a second...")         time.Sleep(time.Second)//休息1s         num := <- unbufChan //接收unbufChan通道元素值         fmt.Printf("Received a integer %d.\n", num)     }()      num := 1     fmt.Printf("Send integer %d...\n", num)     //发送元素值     unbufChan <- num     fmt.Println("Done.") }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

缓冲channel输出结果如下: 
Send integer 1… 
Done. 
====================== 
非缓冲channel输出结果如下: 
Send integer 1… 
Sleep a second… 
Received a integer 1. 
Done.

在非缓冲Channel中,从打印数据可以看出主Goroutine中的发送操作在等待一个能够与之配对的接收操作。配对成功后,元素值1才得以经由unbufChan通道被从主Goroutine传递至那个新的Goroutine.

select与非缓冲通道

与操作缓冲通道的select相比,它被阻塞的概率一般会大很多。只有存在可配对的操作的时候,传递元素值的动作才能真正的开始。 
示例:

发送操作间隔1s,接收操作间隔2s 
分别向unbufChan通道发送小于10和大于等于10的整数,这样更容易从打印结果分辨出配对的时候哪一个case被选中了。下列案例两个case是被随机选择的。

package main  import (     "fmt"     "time" )  func main(){     unbufChan := make(chan int)     sign := make(chan byte, 2)      go func(){         for i := 0; i < 10; i++ {             select {                 case unbufChan <- i:                 case unbufChan <- i + 10:                 default:                     fmt.Println("default!")             }             time.Sleep(time.Second)         }         close(unbufChan)         fmt.Println("The channel is closed.")         sign <- 0     }()      go func(){         loop:             for {                 select {                     case e, ok := <-unbufChan:                     if !ok {                         fmt.Println("Closed channel.")                         break loop                     }                     fmt.Printf("e: %d\n",e)                     time.Sleep(2 * time.Second)                 }             }             sign <- 1     }()     <- sign     <- sign }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

default! //无法配对 
e: 1 
default!//无法配对 
e: 3 
default!//无法配对 
e: 15 
default!//无法配对 
e: 17 
default!//无法配对 
e: 9 
The channel is closed. 
Closed channel.

default case会在收发操作无法配对的情况下被选中并执行。在这里它被选中的概率是50%。

  • 上面的示例给予了我们这样一个启发:使用非缓冲通道能够让我们非常方便地在接收端对发送端的操作频率实施控制。
  • 可以尝试去掉default case,看看打印结果,代码稍作修改如下:
package main  import (     "fmt"     "time" )  func main(){     unbufChan := make(chan int)     sign := make(chan byte, 2)      go func(){         for i := 0; i < 10; i++ {             select {                 case unbufChan <- i:                 case unbufChan <- i + 10:              }             fmt.Printf("The %d select is selected\n",i)             time.Sleep(time.Second)         }         close(unbufChan)         fmt.Println("The channel is closed.")         sign <- 0     }()      go func(){         loop:             for {                 select {                     case e, ok := <-unbufChan:                     if !ok {                         fmt.Println("Closed channel.")                         break loop                     }                     fmt.Printf("e: %d\n",e)                     time.Sleep(2 * time.Second)                 }             }             sign <- 1     }()     <- sign     <- sign }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

e: 0 
The 0 select is selected 
e: 11 
The 1 select is selected 
e: 12 
The 2 select is selected 
e: 3 
The 3 select is selected 
e: 14 
The 4 select is selected 
e: 5 
The 5 select is selected 
e: 16 
The 6 select is selected 
e: 17 
The 7 select is selected 
e: 8 
The 8 select is selected 
e: 19 
The 9 select is selected 
The channel is closed. 
Closed channel.

总结:上面两个例子,第一个有default case 无法配对时执行该语句,而第二个没有default case ,无法配对case时select将阻塞,直到某个case可以运行(上述示例是直到unbufChan数据被读取操作),不会重新对channel或值进行求值。

golang的select典型用法

 golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。

示例:

ch1 := make (chan int, 1) ch2 := make (chan int, 1)  ...  select { case <-ch1:     fmt.Println("ch1 pop one element") case <-ch2:     fmt.Println("ch2 pop one element") } 

注意到 select 的代码形式和 switch 非常相似, 不过 select 的 case 里的操作语句只能是【IO 操作】 。

此示例里面 select 会一直等待等到某个 case 语句完成, 也就是等到成功从 ch1 或者 ch2 中读到数据。 则 select 语句结束。

【使用 select 实现 timeout 机制】

如下:

timeout := make (chan bool, 1) go func() {     time.Sleep(1e9) // sleep one second     timeout <- true }() ch := make (chan int) select { case <- ch: case <- timeout:     fmt.Println("timeout!") } 

当超时时间到的时候,case2 会操作成功。 所以 select 语句则会退出。 而不是一直阻塞在 ch 的读取操作上。 从而实现了对 ch 读取操作的超时设置。

下面这个更有意思一点。

当 select 语句带有 default 的时候:

ch1 := make (chan int, 1) ch2 := make (chan int, 1)  select { case <-ch1:     fmt.Println("ch1 pop one element") case <-ch2:     fmt.Println("ch2 pop one element") default:     fmt.Println("default") } 

此时因为 ch1 和 ch2 都为空,所以 case1 和 case2 都不会读取成功。 则 select 执行 default 语句。

就是因为这个 default 特性, 我们可以使用 select 语句来检测 chan 是否已经满了。

如下:

ch := make (chan int, 1) ch <- 1 select { case ch <- 2: default:     fmt.Println("channel is full !") } 

因为 ch 插入 1 的时候已经满了, 当 ch 要插入 2 的时候,发现 ch 已经满了(case1 阻塞住), 则 select 执行 default 语句。 这样就可以实现对 channel 是否已满的检测, 而不是一直等待。

比如我们有一个服务, 当请求进来的时候我们会生成一个 job 扔进 channel, 由其他协程从 channel 中获取 job 去执行。 但是我们希望当 channel 瞒了的时候, 将该 job 抛弃并回复 【服务繁忙,请稍微再试。】 就可以用 select 实现该需求。

关于垃圾回收

c++ 写久了的人, 刚接触 golang 的时候最不能理解的就是为什么作者要支持垃圾回收。 不管是从垃圾回收器的实现上看, 还是对于程序员编程习惯的养成方面, 都避免不了编写出的程序性能损失。 但是写了几天 golang 之后, 又觉得有垃圾回收确实大大减轻程序员的心智负担, 降低编程门槛,提高编程效率。 让我联想到 汇编 和 C语言 的关系, 即使 C语言的性能不如汇编写出来的高, 但是后者还是颠覆了前者。

golang中的sync.WaitGroup

 刚才看golang的sync的包,看见一个很有用的功能。就是WaitGroup。

先说说WaitGroup的用途:它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。

这里要注意一下,他们的执行结果是没有顺序的,调度器不能保证多个 goroutine 执行次序,且进程退出时不会等待它们结束。

WaitGroup总共有三个方法:Add(delta int),Done(),Wait()。简单的说一下这三个方法的作用。

Add:添加或者减少等待goroutine的数量

Done:相当于Add(-1)

Wait:执行阻塞,直到所有的WaitGroup数量变成0

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
func main() {
    var wg sync.WaitGroup
 
    for i := 0; i > 5; i = i + 1 {
        wg.Add(1)
        go func(n int) {
            // defer wg.Done(),注意这个Done的位置,是另一个函数
            defer wg.Add(-1)
            EchoNumber(n)
        }(i)
    }
 
    wg.Wait()
}
 
func EchoNumber(i int) {
    time.Sleep(3e9)
    fmt.Println(i)
}

golang中的同步是通过sync.WaitGroup来实现的.WaitGroup的功能:它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.

WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待.但是其缺点是无法指定固定的goroutine数目.但是其缺点是无法指定固定的goroutine数目.可能通过使用channel解决此问题。

另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import (
    "fmt"
    "sync"
)
 
//声明一个全局变量
var waitgroup sync.WaitGroup
 
func Afunction(shownum int) {
    fmt.Println(shownum)
    waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}
 
func main() {
    for i := 0; i < 10; i++ {
        waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
        go Afunction(i)
    }
    waitgroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}

http://studygolang.com/articles/2027

Records:47123456