golang学习

作者: ye2012 | 来源:发表于2018-05-01 20:33 被阅读0次

    前言

    因为工作中主要用golang进行开发,所以最近开始学习golang。不得不说,golang是一种灵活简洁的语言,不仅吸取了很多语言的优点,原生支持的goroutine和channel更是极大简化了并发开发。

    也是因为这样,初学leaf框架的时候,遇到了很多问题。虽然在看的过程中遇到不懂的再学也可以慢慢理解框架,但是那样终究还是效率太低,而且理解的很片面,因此我决定系统的学习一下golang的特性,作为我第一篇周记。

    golang特性

    1.指针

    golang的指针与C的指针用法大致相同,不过有几个地方需要注意。

    • golang取消了->,也就是说对于一个对象的元素x,无论p是指针变量还是元素本身,都可以用p.x取到。
    • 也是因为这个原因,对于数组p[],&p的值是这个数组的地址,而不是p[0]的地址。

    2.函数参数及返回值

    golang的函数支持多返回值,不必采用在调用参数中添加指针来获取返回值的方式,让代码可读性更高。golang的结构体函数不是定义在结构体里面,而是单独定义在外面,和普通的函数一样,不过在函数名字前面加上结构体的声明就可以了,类似

    type A struct{
        name string
        age int
    }
    func (obj *A) Hello() (string, int) {
        fmt.Println("Hello I'm " + obj.name)
        return obj.name, obj.age
    }
    

    这样就为A这个结构体定义了一个名为Hello的方法,调用的对象作为指针obj传进函数,返回调用对象的两个属性。

    3.面向对象

    golang在语言层面并没有显示的支持面向对象,但是确实有方法可以实现。

    • 封装在golang中是以包为单位的,首字母大写的函数和变量才能被其他包访问。结构体中的属性小写在json解析的时候不能获取,可以使用json:"keyName" 这种方式生成key值小写的json串。
    type A struct{
        a string `json:"A"`
        B string
        c string
        D string `json:"d"`
    }
    
    func main() {
        obj := A{"a", "b", "c", "d"}
        m_json,_:= json.Marshal(obj)
        fmt.Println(string(m_json))
    }
    

    这样输出的就是 {"B":"b","d":"d"}

    • 继承可以用一种叫做组合的方式实现。组合即在一个结构体Child中声明另一个结构体Father,这样Child就可以直接使用father中的所有方法和属性。
    type father struct {
    Name string
    FamilyName string
    }
    type mother struct {
    Name string
    }
    type son struct{
    father
    mother
    Name string
    }
    func main() {
        son := son{father{"name1", "family" }, mother{"name2"}, "name3"}
        fmt.Println(son.FamilyName)
    }
    

    对于继承的属性,子类可以直接调用,即son.FamilyName直接使用father的属性。但是如果声明的多个结构体中出现同名的属性或方法,就需要在调用的时候指定使用哪个结构体了。可以使用son.father.Name来指定到底调用的是father中定义的Name。

    • 重载不能显性的支持,但是可以使用 args ...interface{} 作为函数参数来支持任意多个参数,其中的interface{}可以代表任意的类型。

    4.Goroutine

    协程可以说是golang最大的特点了。处理高并发的任务,多线程的缺点在于切换的开销太大,而异步的回调机制又比较繁琐,要考虑各种异常情况,容易出问题。协程兼顾二者的有点,提高了程序的开发效率和运行效率。

    下面是在网上找的一张图,基本说明了goroutine的结构。

    协程.jpg

    M:代表真正的内核OS线程,真正干活的人
    G:代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
    P:代表调度的上下文,可以把它看做一个局部的调度器,使go代码在一个线程上跑

    协程可以理解为用户创建的线程,不过他们的调度是在应用层完成的。golang维护了一个线程池,runtime.GOMAXPROCS(n)可以设置池子的大小,默认只有一个,每个线程都维护自己的一个goroutine队列。golang封装了一些异步的系统接口,这样一旦一个正在运行的协程调用了这些系统接口,golang就会把这个协程打包丢到队列里面排队,然后从队列里拿到一个新的goroutine运行。
    golang的协程实现非常方便,只需要go funcName()就完成了一个协程的创建过程。但是要注意的是,和创建线程不同,创建的协程会随着主线程结束而结束。

    5.Channel和sync

    既然有了goroutine来做高并发,自然少不了并发的通信。

    • WaitGroup
      WaitGroup总共有三个方法:
      Add:添加或者减少等待goroutine的数量
      Done:相当于Add(-1)
      Wait:执行阻塞,直到所有的WaitGroup数量变成0
    var waitgroup sync.WaitGroup
    func Solve(event int) {
        fmt.Println(event)
        waitgroup.Done()
    }
    
    func main() {
        for i := 0; i < 10; i++ {
            waitgroup.Add(1) 
            go Solve(i)
        }
        waitgroup.Wait()
    }
    

    这个WaitGroup可以用来确保goroutine都执行完成或者对某个资源进行监控,很像操作系统里的信号量。

    • channel
      channel是golang中一种内置类型,channel一共有4中操作:
      make:创建channel,第二个参数为缓存的大小,缺省值为0
      channel<- :向channel中存入数据
      <-channel:从channel中取出数据
      close:关闭channel
    func DoSth(ch chan int) {
        fmt.Println("finish")
        <-ch
    }
    
    func main() {
        ch := make(chan int)
        go DoSth(ch)
        ch <- 1
    }
    

    首先创建一个channel,缓存大小为0即无缓存,创建goroutine,之后主协程调用ch<-向channel中存入数据,等待创建的协程执行<-ch读取。
    这里说一下缓存的作用,在存入数据的时候,如果有未满的缓存,则只需要阻塞直到数据存入缓存就结束了,但是如果缓存满了,则需要等待数据被读取。

    因为channel的这种阻塞特性,有可能产生死锁,我们必须处理超时的情况。

    go func(){
        DoSth()
        c2 <- "Finish"
    }
    go func() {  
           time.Sleep(time.Second * 1)  
           c1 <- "Time Over"  
    }() 
    select {  
        case msg1 := <-c1:
            fmt.Println("time out", msg1)  
        case msg2 := <-c2:
            fmt.Println("received", msg2)  
    }  
    
    

    这里的select会轮询两个条件,直到有一个满足,则执行对应的操作。如果DoSth在一秒内执行完成,c2中会存入数据,select执行输出received的操作,相反过了一秒DoSth没完成,c1中存入数据,select收到c1的数据,就执行输出time out的操作。

    select {  
        case msg1 := <-c1:
            fmt.Println("time out", msg1)  
        case msg2 := <-c2:
            fmt.Println("received", msg2)  
        default:
            fmt.Println("default")
    }  
    

    如果select中定义了default标签,select立即返回结果,如果c1,c2都为空,则执行default操作。

    总结

    除了这里的这些特性,学习golang自然还少不了众多的第三方的包。不过这个要在以后的日子里一点一点积累。
    这次总结知识梳理了一下golang的一些用法,要想真正掌握这些特性,还是需要把这些东西结合到实际的应用中。

    相关文章

      网友评论

        本文标题:golang学习

        本文链接:https://www.haomeiwen.com/subject/xwdfrftx.html