美文网首页
Go学习笔记

Go学习笔记

作者: adrian920 | 来源:发表于2018-04-05 18:12 被阅读246次

    使用go1.10版本,在liteIde里开发。

    1,变量声明后必须使用,不然编译不过(全局变量可以不用)。

    2,变量可以不用var关键字(简短形式),如c := 66,但是c必须是没有声明过的,而且c必须在函数中

    3, go1.9之后,数字类型可以不加类型关键字,系统自动推断。

    4, var(a,b = 9, 99)// 因式分解的写法,一般用于声明全局变量,同一行可以同时声明多个变量,叫并行或同时赋值

    5,像int, float, bool,string这些是值类型,这些类型的变量直接指向内存中的值(栈中)。当使用=赋值的时候,实际是在内存中将等号右边的值进行了拷贝。两边地址不一样。更复杂的数据类型,需要使用多个字,这些数据一般使用引用类型保存。如指针类型p1,它的值是一个地址a,指向真正的值v,p2=p1后,拷贝的只是a,指向的仍是同一个真正的值v

    6,由于go函数可以多返回,有时候又不需要所有的值,(局部变量声明不用要报错)可以用_表示抛弃值 _,b = 5,7,5就被抛弃

    7,常量const关键字除了用于变量不可修改,还可用于枚举,如 const(Unknown =0, Femal = 1, Male = 2)

    8,iota,特殊常量,可以认为是可以被编译器修改的常量。每当const关键字出现时,被重置为0,在下一个const出现前,每出现一次iota,所代表的数字自动加1,const(a = iota;b = iota;c = iota ),a,b,c的值依次为0,1,2,后面两个iota可以省略

    9,<< ,>>,左移,右移双目运算符,相当于乘以,除以2的n次方(n为移动位数)。

    10,select,条件判断语句,跟switch类似,只是select会随机运行一个case,如果没有case,就阻塞,知道有一个case为止,select后不加判断条件。

    11,函数定义格式 fund funcName(x type, y type)(type, type){},只返回一个值的话,返回类型括号可以不要。第一个参数的type可以不要,如果两个参数类型一致。

    匿名函数命名格式区别是仅仅没有函数名。调用不太一样,如下调用:func(index int){xxx}(paras),paras是参数列表,没有参数就只写小括号。小括号表示匿名函数被调用。如:x, y := func(i, j int) (v1, v2 int) {

            return i + 1, j + 1

        }(9, 99) // 表示直接传入9,99两个参数调用这个匿名函数,x,y的结果分别是10,100

    12,函数参数的值传递和引用传递:值传递=调用函数时将实际参数复制一份传递到函数中,这样在函数中对参数的修改不会影响实际参数;引用传递=调用函数时将实际参数的地址传递到函数中,这样在函数中对参数的修改会阴影实际参数。

    13,函数作为值:

    func maxValue(x, y int) int {

        if x > y {

            return x

        }

        return y

    }

    fmt.Print(maxValue(11,22)) // 输出22

    14, 函数闭包:go支持匿名函数,可作为闭包。匿名函数是一个内联语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

    func getSequence() func() int{

    i += 0

    return func() int{

    i += 1

    return i

    }

    }

    nextNum := getSequence() // nextNum 是返回的一个函数

    14,方法:方法就是包含接受者的函数。接受者可以是命名类型或者结构体类型的一个值或指针。所有给定类型的方法属于该类型的方法集(感觉就是一个对象的方法)

    type Circle struct{

    radius float64

    }

    fun (c Circle) getArea() float64{

    return 3.14 * c.radius * c.radius

    }

    fun main(){

    var c1 Circle

    c1.radius = 10.00

    var area = c1.getArea()

    }

    15,数组:定义格式 var arrayName[n] type

    var a = [5] float32{1,2,3,4,5}

    var a1 = […] float32{1,2} // 不设置数组大小,会根据元素个数自动设置。

    16,指针:变量是一个占位符,用于引用计算机内存的地址。

     指针变量定义:var varName *type ,var fp *float。空指针用nil表示

    指针数组=数组中每个元素指向一个值(每个元素都是一个指针), var ptr [n] *int

    17,结构体定义 var structName struct{}

    18,切片(slice,就是动态数组),切片不需要说明数组长度。当需要容量大且内容频繁修改的时候,用list更好

    定义格式:var s []type 或者用make()创建,var s []type = make([]type, len)

    直接初始化: s := [] int {1,2}

    其它数组的引用: s := arr[:]

    根据其它数组的元素创建新的切片: s := arr[startIndex:endIndex], 一个不写表示前面或后面所有的

    通过一个切片创建一个新的切片: s  := s1[startIndex:endIndex],新片中包含startIndex表示的值,不包含endIndex包含的值。s1也可以是一个数组,slice本来也就是基于数组的一个抽象数据类型,是一个数组片段的描述。一个slice结构包括三部分,一个指向底层数组的指针ptr,一个切片的长度len,一个底层数组的长度cap。切片是可以索引的。

    make创建: s := make([]int, len, cap) // len表示元素数据个数,默认0,表示最大容量

    19,追加切片append():s = append(s, 1, 2), 这里追加两个元素,len+2,cap+4(如果新的切片大小是旧的2倍以上,cap=len,否则,len<1024,cap翻倍,大于1024,cap翻1.25倍)。如果切片作为函数参数,如s是函数外参数,s1是函数参数名,这个s1其实是s的一个复制。但是在函数内,s1[1] = 66这种操作是可以修改s的内容的。因为切片里是根据一个数组指针表示数组的,复制切片后指针指向的地址没变。但是如果用append,增加的只是s1的容量,长度,s不变,所以如果打印,s看起来没变。如果要在函数内根据append修改函数外的值,需要这样写:

    func test(s *[]int){*s = append(*s, 22)}

    20,切片拷贝copy(): copy(s,s1), 表示把s1中的数据拷贝到s中,s1中的数据顺序不变,从起始位置替换s中的元素。

    21,范围(range):range可以枚举数组,切片,unicode支付查等中的索引值和元素值

    for i,c := range “go”

    22,map:无序键值对的集合,用hash表实现,可以迭代

    定义格式:var mapName map[keyType] valueType, 可以用make创建,m := make(map[keyType] valueType)

    如 m := make(map[string]string) m[“name”] = “xx”

    range可以枚举它 for key := range m;

    还可以查看是否有一个key对应的值 value,ok := m[‘name’],  ok是bool值,表示值是否存在

    23,接口:一种数据类型,它把所有具有共性的方法定义在一起,任何其它类型只要实现了这些方法就是实现了这个接口,一个应用例子如下:

    type Phone interface {

        call()

    }

    type NokiaPhone struct {

    }

    func (nokiaPhone NokiaPhone) call() {

        fmt.Println("I am Nokia, I can call you!")

    }

    type IPhone struct {

    }

    func (iPhone IPhone) call() {

        fmt.Println("I am iPhone, I can call you!")

    }

    func main() {

        var phone Phone

        phone = new(NokiaPhone)

        phone.call(

        phone = new(IPhone)

      phone.call()

    }

    24,defer 语句会延迟函数的执行直到上层函数返回。延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

    25,go语言的并发支持

    go语言相比java等一个巨大的优势就是可以方便的编写并发程序。go语言内置了goroutine机制,可以快速的开发并发程序,更好的利用多核处理器资源。

    go语言让事件在新线程中执行变得简单,调用函数的时候前面加上 go 关键字就可以了。但是说到多线程,就不得不说线程之间的通信,我们很多时候要知道子线程何时结束,执行的结果以实现线程的同步。go里用channel(信道)的概念。

    和map一样,信道是引用类型,用make分配内存,创建的时候还可以加一个可选的整型参数,以限制缓存区大小。默认为0.

    c := make(chan int) // 无缓存整型信道 ;c := make(chan *os,File, 100) // 缓冲的文件指针信道

    可以往信道中插入数据 c <- 1; 也可以从信道中读数据 <- c,

    例子:

        c := make(chan int)

        go func() {

            for i := 0; i < 10; i++ {

                fmt.Print("go goroutine test......", i, "\n")

            }

            c <- 1

        }()

        fmt.Print("waiting...\n")

        <-c

        fmt.Print("go routine over")

    打印结果是:

         waiting… 

         go gorouting test…….0

    ……

    go grouting test….9

    go routine over// 如果没有<-c,会一直阻塞。如果信道是非缓存的,则发信者在收信者接收到数据前也一直阻塞。如果信道有缓冲区,发信者只有在数据被填入缓冲区前才被阻塞,如果缓冲区是满的,意味着发信者要等到某个收信者取走一个值

    限制channel的吞吐量可以用两种方式,

    1,创建的时候添加参数设置。 var see = make(chan int, maxNum)

    2,  通过循环,把go func xxx放到循环内

    信道可以像其它类型的数值一样被分配内存并传递,此特性常用于实现安全且并行的去复用(demultiplexing)。

    3,runtime.GOMAXPROCS(n)n小于cpu核数,显示的设置是否使用多核来执行并发任务

    防止主goutine过早的被运行结束的有效手段之一—同步(sync.WaitGroup)

    var waitGroup sync.WaitGroup // 用于等待一组操作执行完毕的同步工具

    waitGroup.Add(3) // 改组操作的数量3.

    chan1 := make(chan int64, 3) // 数字通道1

    chan2 := make(chan int64, 3) // 数字通道2

    chan3 := make(chan int64, 3) // 数字通道3

    sync.WaitGroup代表一个类型,该类型声明存在于代码包sync中,类型名为WaitGroup,add(3)表示我们后面要启用3个

    groutine。

    下面以一个groutine处理完成再交给第二个grouting为例:

    第一个处理:

    go fun(){

    // 从chan1获取处理对象

    process

    // 把结果传给第二个chan2

    close(chan2)// 关闭chan2

    waitGroup.done()// 表示此操作完成,相当于从group中的3减去1

    }

    第二,三个处理一次接受上一个chan,传给下一个。

    调用的时候, 向chan1中传递一个一个数据,完成后关闭chan1.

    为了能让这个流程执行完成,末尾还需要加上:

    waitGroup.Wait()// 等待前面那组操作(一共3个)的完成。

    25,go语言并发机制

    线程和进程

    现在操作系统中,线程是处理调度和分配的基本单位,进程是资源分配的基本单位。每个进程由私有的虚拟空间,代码,数据和其它各种系统资源组成。线程是进程内的一个执行单元。每个进程至少有一个主线程,系统自动创建。用户根据需要创建其它线程。多个线程并发地运行于同一个进程中。

    并行与并发

    并发,一个时间段内有很多的线程或进程执行,但在任何时间点上都只有一个在执行,多个线程或进程争抢时间片轮流执行。并行,一个时间段和时间点上都有多个线程或进程在执行。

    并行需要硬件支持,单核只能并发。

    并发是并行的必要条件,如果一个程序本身就不并发,也就是只有一个逻辑执行顺序,那么不可能让其并行处理。

    并发不是并行的充分条件,一个并发程序,还需要多核的支持才能并行。

    线程模型的3个分类

    线程有用户线程和内核级线程两类。

    第一类,多对一模型:多个用户线程映射到一个内核线程,线程管理在用户空间完成,此模式下,用户线程对os透明,这种模型,好处是:线程上下文切换都发生在用户空间,避免模态切换,对于性能有积极影响。缺点是:所有的线程基于一个内核调度实体(即内核线程),这样只有一个处理器被利用,效率低下,并且一个线程阻塞,其它都要等待。

    第二类,一对一模型:摸个用户线程映射一个内存线程,每个线程有内核调度器独立调度。这种模型,好处是:多核下,支持并行,效率高,一个线程阻塞,允许其它继续执行。缺点是:每创建一个用户线程都需要创建一个系统线程对应,线程创建的开销大,影响程序性能。

    第三类,多对多模型:用户线程和内核线程都有多个(部分用户线程映射一个内核线程)。结合前两种的优点。这种模型需要内核线程调度器和用户线程调度器相互操作,本质上是多个线程被绑定到了多个内核线程上,使得大部分的线程上下文切换发生在用户空间,而多个内核线程又可以充分利用处理器资源。

    groutine就采用了第三类模型。grouting机制是协程的一种实现,golang内置调度器,可以让多核cpu中每个cpu执行一个协程。

    调度器工作方式

    整个调度,包括四个重要部分,M,G, P, sched:

    sched是调度器,它维护存储M,G的队列和调度器的一些状态信息

    M是系统线程,有os管理,goroutine就是在M上运行。

    P是处理器,主要用途就是来执行grouting的,它维护一个grouting队列,即run queue。

    G是goroutine实现的核心结构,包含栈,指令指针等,代表一个goroutine 

    单核情况下,一个M,一个P,若干个G排队。一个G运行完自己的时间片后,让出上下文,回到runqueue中。多核中,有多个M,每个m有一个p。

    线程阻塞情况下,会在创建一个M1,当前的M放弃它的P,P转到M1中去运行。

    当一个p的runqueue为空,没有G的时候,P会从另一个上下文偷取一半的G执行。

    相关文章

      网友评论

          本文标题:Go学习笔记

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