这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
1.并发与并行
1.并发:多个线程在一个核的cpu下运行 2.并行:多个线程在多个核的cpu下运行
2.线程与协程
2.1简介
1.线程是cpu调度的一个基本单位,线程不拥有自己的系统资源,它与同属于同一进程的其他线程共享进程所拥有的全部资源,多个线程之间通过共享内存等线程间的通信方式来通信,线程拥有自己独立的栈和共享的堆。
2.协程是轻量级线程,一个线程可以拥有多个协程,与线程相比,协程不受操作系统调度,协程调度器按照调度策略把协程调度到线程中执行,协程调度器由应用程序的runtime包提供,用户使用go关键字即可创建协程。
相对来说,协程存在于用户态,切换效率较线程高。
2.2创建协程
使用 go func(){具体实现代码}()可创建一个协程。当一个子协程创建后,它会与主协程互相竞争cpu,导致每一次输出的顺序都不同
func main() {
go func() {
for i := 0; i < 10; i++ {
fmt.Println("子协程:",i)
}
}()
for i := 0; i < 10; i++ {
fmt.Println("主协程:",i)
}
}
除此之外,也可以使用go 函数名()的方法直接使一个已经定义好的函数作为一个子协程开始工作。
func hello(i int) {
for i := 0; i < 10; i++ {
fmt.Println("子协程:",i)
}
}
func main() {
go hello(0)
for i := 0; i < 10; i++ {
fmt.Println("主协程:",i)
}
}
2.3协程执行
如下图所示,当主协程中开启一个子协程后,预期输出为
子协程hello
主协程hello
但是实际上只输出了一句主协程hello
func hello() {
fmt.Println("子协程hello")
}
func main() {
go hello()
fmt.Println("主协程hello")
//time.Sleep(time.Second)
}
这是因为程序开始执行时,cpu会首先调度主协程,运行到go hello()语句时将子协程放进本地协程队列中等待调度,但是主协程执行完所有语句后会直接结束程序,这时子协程就没机会被调度了。
解决方法很简单,只需要在主协程的最后加上一个time.sleep()方法使主协程休眠一段时间即可,如取消上段代码的注释内容。主协程休眠之后,cpu会随机寻找各个协程本地队列的每一个协程进行调度,这时子协程才有机会执行。
2.4协程安全
协程执行时有着隐藏的安全问题。如下段代码所示,定义有两个方法实现对全局变量x的累加,其中一个方法加有锁,每进行一次累加之前加锁使协程强制占用cpu资源直至解锁。加锁方法可以稳定使预期输出为6000,而不加锁的方法每次都会输出不一样的值,存在着安全隐患。
var x int
var lock sync.Mutex
func addWithLock() {
for i := 0; i < 1000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 1000; i++ {
x += 1
}
}
func main() {
x = 0
for i := 0; i < 6; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
fmt.Println("addWithoutLock:", x)
x = 0
for i := 0; i < 6; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("addWithLock:", x)
}
2.5通道channel
通道channel是go语言中的一种特殊类型,由具体数据类型定义的处理协程的结构,类似于队列,遵循先进先出的规则。
//声明规则
var 变量 chan 元素类型
使用无缓冲通道发送、接收数据。一个协程发送值,另一个协程接收值,此时如果任意一个协程没有被执行都会进入阻塞状态。如果发送协程执行后会进入阻塞,直到接收协程成功接收数据,或如果接收协程先执行则会进入阻塞,直到发送协程给其发送数据。
func recv(i chan int) {
num := <-i
fmt.Println("已接收", num)
}
func main() {
ch := make(chan int)
go recv(ch)
ch <- 10
fmt.Println("已发送")
}
除此之外还可以建立一种有缓冲通道,定义时需要给定一个容量参数,意味着能够使通道暂时存储所传入的数据,只有数据量达到容量后才会发生阻塞。
ch := make(chan int, 1)
总结
本次学习学到了go语言关于协程的相关知识,协程作为一种轻量级线程,运行效率比线程要高,对于拥有高并发特性的go语言来说有着非常重要的地位。学习协程需要搞清楚协程的底层调用原理,比如资源抢占、协程休眠、调用顺序等相关知识点。