Go语言基础|青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
- 协程:
协程,英文Coroutines,是一种比线程更加轻量级的存在。 协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。--出处
GO的协程使用非常简单,直接在函数前加上关键字go即可。
go FunctionName(j int){
}(i)
其中,j为函数的形参,i为函数的实参。
当协程创建后会立即执行,与主线程是分开的。 如果,这个协程在一个for循环内部,比如:
for i:=1;i<=10;i++{
go Print(j int){
fmt.PrintLn(j)
}(i)
}
由于每一个协程(这里有10个)与主线程都是同时执行的,如果不加括号里的i为实参,输出不一定为1-10。(当然输出的顺序可能改变)
2.通道(channel)
协程之间通过通信实现共享内存,通信依赖于通道;而不是通过共享内存实现互相通信(多个协程同时访问一个内存可能出错)。
通道有有无缓存区的区别,如下图:
src:=make(chan int)//无缓存区
dest:=make(chan int,3)//3个int大小的缓存区
无缓存区直接通信,有缓存区会先存储在缓存区中。比如在协程产生数据,主线程接受数据时,主线程接受的速度没有产生的快,此时就应该设置缓存区。
3.并发安全
当多个协程同时访问一个共享数据时,可能会出现问题。(Java的多线程也有类似问题)因此,在一个协程访问这个资源时,应该先“锁”住,访问结束后再“解锁”。
lock sync.Mutex //定义一个互斥锁
lock.Lock()//锁
lock.Unluck() //解锁
互斥锁:无论读写同一时间内只有一个协程可以对当前数据进行操作。在这种情况下,如果一个协程耗费太多时间,会导致程序性能下降。为此,GO语言提供了读写锁。
读写锁:在读多写少的情况下,不需要每次都用互斥锁。使用读写锁,多个协程可以同时读取数据。要获取写锁,必须等所有读锁失效,获取读锁,等写锁失效。
即读写互斥。
var rwLock sync.RWMutex
rwLock.RLock()//获取读锁
rwLock.RUnlock()//释放读锁
rwLock.Lock()//获取写锁
rwLock.Unlock()//释放写锁
4.WaitGroup
当协程在执行时,主线程可能没有在所有协程结束之前就结束了。为了解决这个问题,我们可以在线程结束之前加上time.sleep,但是时间的控制成了一个问题。
为此我们可以使用WaitGroup,它本质上是一个计数器,可以加一减一。
var wg sync.WaitGroup //定义
//常用方法
wg.add(delta int)//初始时先加上总的协程数
wg.Done()//一个协程结束便调用
wg.Wait()//主线程最后使用wait,当计数器到0是结束
此时一般在协程的defer里调用Down()。顺带一提defer,一直不明白既然是结束为什么要写在前面。
defer是一个栈,如果有多条defer,按照入栈顺序,先执行后入栈的再执行先入栈的。在一个协程中,如果有错误(此时用panic输出错误),协程就不会继续执行下去了。但是,defer的语句是会执行完的。因此,一些必须要释放的资源可以放在defer里释放,以免出现问题。
另外,panic在释放错误后,可用 recover"恢复”,使程序继续运行不至于崩溃。
recover只能在defer后使用,在这一协程报错时,会捕获到错误,错误之后的代码不执行,而此协程之外的(比如主线程)会继续正常执行,