goLang学习笔记(三)

225 阅读9分钟

二十一:go协程

1、go协程是什么
    go协程是与其他函数或方法一起并发运行的函数或方法。go协程可以看作是轻量级线程。与线程相比
    创建一个go协程的成本很小

2、go协程相比线程的优势
    a:相比线程而言,go协程的成本极低。堆栈大小只有若干kb,可以根据应用的需求进行增减。
        而线程必须指定堆栈的大小,其堆栈固定不变

    b:go协程会复用数量更少的os线程。即使程序有数以千计的go协程,也可能只有一个线程。
        如果go协程发生来阻塞,系统会再创建一个os线程,并把其余go协程都移到这个新的OS线程

    c:go协程使用信道来进行通信。信道用于防止多个协程访问共享内存时发生竞态条件,信道可以
        看作是go协程之间通信的管道

3、启动go协程
    调用函数或者方法时,在前面加上关键字go,可以让一个新的go协程并发的运行

    1、启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待go协程执行完毕。
        在调用go协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值

    2、如果希望运行其他的go协程,go主线程必须继续运行着。如果go主协程终止,则程序终止,于是其他
        go协程不会继续运行

    3、在go主线程中使用休眠,以便等待其他线程执行完毕,只是用于理解go协程如何工作的技巧。
        信道可用于在其他协程结束执行之前阻塞go主协程
        func main(){
            go hello()
            time.Sleep(1 * time.Second)
            fmt.Println("xsxs")
        }

4、启动多个go协程

二十二:信道

信道可以想象成go协程之间通信的管道
1、信道的声明
    所有信道都关联来一个类型。信道只能运输这种类型的数据,而运输其他类型的数据是非法的
    chan T表示T类型的信道
    定义信道: a := make(chan int)

2、通过信道进行发送和接收
    data := <- a //读取信道a
    a <- data //写入信道a

3、发送与接收默认是阻塞的
    a:当数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它go协程从信道读取数据,
    才会解除阻塞
    b:当读取信道的数据时,如果没有其它的协程把数据写入到信道,那么读取过程就会一直阻塞
    帮助go协程之间高效的通信

4、死锁
    a:go协程给一个信道发送数据时,照理说会有其它go协程来接收数据,如果没有程序就会在运行时触
        发panic,形成死锁
    b:同理,接收时也一样

5、单向信道
    只能发送或者接收数据
    a:信道转换:把一个双向信道转换程唯送信道或者唯收信道

6、关闭信道和使用for range遍历信道
    a:数据发送方可以关闭信道,通知接收方这个信道不再有数据发送过来
    b:从信道接收数据时,接收方可以多用一个变量来检查信道是否已经关闭
        v, ok := <- ch

二十三:缓冲信道和工作池

1、缓冲信道
    a:无缓冲信道的发送和接收过程都是阻塞的
    b:有缓冲的信道:
        一:只在缓冲已满的情况,才会阻塞向缓冲信道发送数据
        二:只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据

    c:创建缓冲信道
        ch := make(chan type, capacity)  capacity表示容量,无缓冲信道默认是0


2、死锁
    func main(){
        ch := make(chan string, 2)
        ch <- "nav"
        ch <- "pal"
        ch <- "ste"
        fmt.Println(<-ch)
        fmt.Println(<-ch)
    }
    向容量为2 的缓冲信道写入3个字符串,程序在第三次写入时,由于超出来信道的容量,因此这次写入发生
    了阻塞。
    想要这次写操作进行下去,必须要有其他协程来读取这个信道的数据,所有这里发生死锁

3、长度和容量
    a:缓冲信道的容量是指信道可以存储的值的数量   make函数创建时指定
    b:长度是指当前排队的元素个数

4、waitGroup
    a:waitGroup用于实现工作池,用于等待一批go协程执行结束。程序控制会一直阻塞,直到这些协程全部
    执行完毕
    b:传递wg的地址是很重要的。如果没有传递wg的地址,那么每个go协程将会得到一个waitGroup值的拷贝,
        因而当它们执行结束时,main函数并不会知道
        go process(i, &wg)

5、工作池的实现
    a:缓冲信道的重要作用之一就是实现工作池。工作池就是一组等待任务分配的线程

    b:工作池的核心功能:
        一:创建一个go协程池,监听一个等待作业分配的输入型缓冲信道
        二:将作业添加到该输入型缓冲信道
        三:作业完成后,再将结果写入一个输出型缓冲信道
        四:从输出型缓冲信道读取并打印结果

    c:随着工作协程数量的增加,完成作业的总时间会减少

二十四:select

1、select语句用于在多个发送/接收信道操作中进行选择。select语句会一直阻塞,直到发送/接收操作准备
    就绪。
    如果有多个信道操作准备完毕,select会随机选取其中之一执行

2、select应用

3、默认情况
    在没有case准备就绪时,可以执行select语句中的默认情况,防止select语句一直阻塞

4、死锁与默认情况
    func main(){
        ch := make(chan string)
        select {
        case <- ch:
        }
    }
    a:如果存在默认情况,就不会发生死锁
    b:如果select只包含值为nil的值,也同样会执行默认情况

5、随机选取
    当select由多个case准备就绪时,将会随机选取其中之一去执行
    在playground上运行不具有随机性

6、空select
    select语句没有任何case,会一直阻塞,导致死锁

二十五:mutex

1、临界区
    当程序并发运行时,多个go协程不应该同时访问那些修改共享资源的代码

2、mutex用于提供一种加锁机制,可确保某时刻只有一个协程在临界区运行,防止出现竞态条件
    A:mutex定义了两个方法:Lock和Unlock。所有在Lock和Unlock之间的代码,都只能由一个go协程执行,
    可以避免竞态条件

3、使用mutex
    传递mutex的地址很重要,如果传递的是mutex的值而非地址,那么每个协程都会得到mutex的一份拷贝,
    竞态还是会发生

4、使用信道处理竞态条件

5、mutex vS 信道
    当go协程需要与其他协程通信时,可以使用信道
    而当只允许一个协程访问临界区时,可以使用mutex

二十六:结构体取代类

go有类型和方法,支持面向对象的编程风格,却没有类型的层次结构
1、go不支持类,而是提供类结构体
    a:结构体中可以添加方法,这样可以将数据和操作数据的方法绑定在一起,实现与类相似的效果

2、使用New()函数,而非构造器
    a:java中使用构造器来解决,一个合法的对象必须使用参数化的构造器来创建
    b:go不支持构造器,如果某类型的零值不可用,需要程序员来隐藏该类型,避免从其他包直接访问
        提供一种名为NewT(parameters)的函数,按照要求来初始化T类型的变量

    c:go不支持类,但是结构体能很好的取代类,以New签名的方法可以替代构造器

二十七:组合取代继承

go不支持继承,但它支持组合
1、通过嵌套结构体进行组合
    a:在go中,通过在结构体嵌套结构体,可以实现组合
    b:一旦一个结构体内嵌套了一个结构体字段,go可以使我们访问其嵌套的字段。

2、结构体切片的嵌套
    结构头切片不能嵌套一个匿名切片

二十八:多态

go通过接口实现多态,在go中隐式的实现接口。一个类型如果定义了接口所声明的全部方法,它就实现了该接口

1、使用接口实现多态
    a:一个类型如果定义了接口的所有方法,那它就隐式的实现了该接口
    b:所有实现了接口的类型,都可以把它的值保存在一个接口类型的变量中。使用接口的这种特性实现多态

二十九:defer

1、defer的用途:含有defer语句的函数,会在该函数将要返回直接,调用另一个函数
2、延迟方法
3、实参取值
    在go语言中,当执行defer语句的时候,就会对延迟函数的实参进行求值

4、defer栈
    当一个函数多次调用defer时,go会把defer调用放到一个栈中,按后进先出的顺序执行

5、defer的实际应用
    当一个函数应该在与当前代码流无关的环境下调用时,可以使用defer

三十:错误处理

1、按照go的惯例,在处理错误时,通常都是将返回的错误与nil比较。nil表示没有发生错误
2、从错误获取更多信息的不同方法
    a:断言底层结构体类型,使用结构体字段获取更多信息 PathError
    b:断言底层结构体类型,调用方法获取更多信息   DNSError
    c:直接比较
3、不可忽略的错误