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:随着工作协程数量的增加,完成作业的总时间会减少
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,会一直阻塞,导致死锁
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:所有实现了接口的类型,都可以把它的值保存在一个接口类型的变量中。使用接口的这种特性实现多态
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、不可忽略的错误