加载中…
个人资料
我行我易
我行我易
  • 博客等级:
  • 博客积分:0
  • 博客访问:4,280
  • 关注人气:1
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

go语言笔记(第九记)

(2013-11-28 09:55:02)

interface函数参数
interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。

interface变量存储的类型

我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

Comma-ok断言

Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。

如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。

嵌入interface

Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Struct时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到interface里面,那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。

反射

Go语言实现了反射,反射就是动态运行时的状态。我们一般用到的包是reflect包。

使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:


t := reflect.TypeOf(i)    //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i)   //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值

转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如

tag := t.Elem().Field(0).Tag  //获取定义在struct里面的标签
name := v.Elem().Field(0).String()  //获取存储在第一个字段里面的值

获取反射值能返回相应的类型和数值

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

如果要修改相应的值,必须这样写

var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

并发

goroutine

goroutine是Go并行设计的核心。goroutine说到底其实就是线程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。

执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过go关键字实现了,其实就是一个普通的函数。

go hello(a, b, c)

通过关键字go就启动了一个goroutine。我们来看一个例子

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world") //开一个新的Goroutines执行
    say("hello") //当前Goroutines执行
}

输出:
hello
world
hello
world
hello
world
hello
world
hello

我们可以看到go关键字很方便的就实现了并发编程。上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。

runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。

默认情况下,调度器仅使用单线程,也就是说只实现了并发。想要发挥多核处理器的并行,需要在我们的程序中显式调用 runtime.GOMAXPROCS(n) 告诉调度器同时使用多个线程。GOMAXPROCS 设置了同时运行逻辑代码的系统线程的最大数量,并返回之前的设置。如果n < 1,不会改变当前设置。以后Go的新版本中调度得到改进后,这将被移除。这里有一篇Rob介绍的关于并发和并行的文章:http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide

channels

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

channel通过操作符<-来接收和发送数据

ch <- v    // 发送v到channel ch.
v := <-ch  // 从ch中接收数据,并赋值给v

我们把这些应用到我们的例子中来:

package main

import "fmt"

func sum(a []int, c chan int) {
    total := 0
    for _, v := range a {
        total += v
    }
    c <- total  // send total to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c  // receive from c

    fmt.Println(x, y, x + y)
}

默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。

Buffered Channels

上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。

Range和Close

上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

上面代码我们看到可以显式的关闭channel,生产者通过关键字close函数关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法v, ok := <-ch测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。

记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic

另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的

Select

我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字select,通过select可以监听channel上的数据流动。

select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。

select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

select {
case i := <-c:
    // use i
default:
    // 当c阻塞的时候执行这里
}

超时

有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}

runtime goroutine

runtime包中有几个处理goroutine的函数:

  • Goexit

    退出当前执行的goroutine,但是defer函数还会继续调用

  • Gosched

    让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

  • NumCPU

    返回 CPU 核数量

  • NumGoroutine

    返回正在执行和排队的任务总数

  • GOMAXPROCS

    用来设置可以并行计算的CPU核数的最大值,并返回之前的值。

 

这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。


break    default      func    interface    select
case     defer        go      map          struct
chan     else         goto    package      switch
const    fallthrough  if      range        type
continue for          import  return       var

  • var和const参考2.2Go语言基础里面的变量和常量申明
  • package和import已经有过短暂的接触
  • func 用于定义函数和方法
  • return 用于从函数返回
  • defer 用于类似析构函数
  • go 用于并行
  • select 用于选择不同类型的通讯
  • interface 用于定义接口,参考2.6小节
  • struct 用于定义抽象数据类型,参考2.5小节
  • break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考2.3流程介绍里面
  • chan用于channel通讯
  • type用于声明自定义类型
  • map用于声明map类型数据
  • range用于读取slice、map、channel数据

上面这二十五个关键字记住了,那么Go你也已经差不多学会了。

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有