GO并发编程问题
并行和并发
并行(parallel):(并行就是两个队列同时使用2个厕所)
指在同一时刻(CPU时间量级 ),有`多条指令`在`多个处理器`上同时执行。需要借助多核CPU来实现
并发:(就是两个队列交替使用一间厕所)
宏观:用户体验上,程序在并行执行。
微观:多个计划任务,顺序执行。在飞快的切换。轮换使用 cpu 时间轮片。 (类似于串行)
进程并发
程序和进程?
程序:编译成功得到的二进制文件。 占用 磁盘空间。 死的 1 1
进程:运行起来程序。 占用系统资源。(内存) 活的 N 1
可以理解成:
程序 → 剧本(纸) 进程 → 戏(舞台、演员、灯光、道具...)
进程状态
进程基本的状态有5种。分别为初始态、就绪态、运行态、挂起(阻塞)态、终止(停止)态。其中初始态为进程准备阶段,常与就绪态结合来看。
同步:协同步调。规划先后顺序。
线程同步机制:
互斥锁(互斥量):建议锁。 拿到锁以后,才能访问数据,没有拿到锁的线程,阻塞等待。等到拿锁的线程释放锁。
读写锁:一把锁(读属性、写属性)。 写独占,读共享。 写锁优先级高。
Go语言中的并发程序主要使用两种手段来实现。goroutine
和channel
。
goroutine 又叫做go程 创建于进程中,直接使用 go 关键,放置于 函数调用前面,产生一个 go程。 并发
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延时1s
}
}
func main() {
//创建一个 goroutine,启动另外一个任务
go newTask()
i := 0
//main goroutine 循环打印
for {
i++
fmt.Printf("main goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延时1s
}
}
Goroutine的特性:【重点】主go程结束,子go程随之退出
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine: i = %d\n", i)
time.Sleep(1 * time.Second) //延时1s
}
}
func main() {
//创建一个 goroutine,启动另外一个任务
go newTask()
fmt.Println("main goroutine exit")
}
runtime包
- runtime.Gosched():
出让当前go程所占用的 cpu时间片。当再次获得cpu时,从出让位置继续回复执行。
—— 时间片轮转调度算法。
package main
import (
"fmt"
"runtime"
)
func main() {
//创建一个goroutine
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}("world")
for i := 0; i < 2; i++ {
runtime.Gosched() //import "runtime" 包
/*
屏蔽runtime.Gosched()运行结果如下:
hello
hello
没有runtime.Gosched()运行结果如下:
world
world
hello
hello
*/
fmt.Println("hello")
}
}
以上程序的执行过程如下:
主协程进入main()函数,进行代码的执行。当执行到go func()匿名函数时,创建一个新的协程,开始执行匿名函数中的代码,主协程继续向下执行,执行到runtime.Gosched( )时会暂停向下执行,直到其它协程执行完后,再回到该位置,主协程继续向下执行。
- runtime.Goexit():
return: 返回当前函数调用到调用者那里去。 return之前的 defer 注册生效。
Goexit(): 结束调用该函数的当前go程。Goexit():之前注册的 defer都生效。
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
runtime.Goexit() // 终止当前 goroutine, import "runtime"
fmt.Println("B") // 不会执行
}()
fmt.Println("A") // 不会执行
}() //不要忘记()
//死循环,目的不让主goroutine结束
for {
}
}
- GOMAXPROCS
调用 runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
package main
import (
"fmt"
)
func main() {
//n := runtime.GOMAXPROCS(1) // 第一次 测试
//打印结果:111111111111111111110000000000000000000011111...
n := runtime.GOMAXPROCS(2) // 第二次 测试
//打印结果:010101010101010101011001100101011010010100110...
fmt.Printf("n = %d\n", n)
for {
go fmt.Print(0)
fmt.Print(1)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210221145313977.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BwcHBwcHVzaGNhcg==,size_16,color_FFFFFF,t_70)
}
}
在第一次执行runtime.GOMAXPROCS(1) 时,最多同时只能有一个goroutine被执行。所以会打印很多1。过了一段时间后,GO调度器会将其置为休眠,并唤醒另一个goroutine,这时候就开始打印很多0了,在打印的时候,goroutine是被调度到操作系统线程上的。
在第二次执行runtime.GOMAXPROCS(2) 时, 我们使用了两个CPU,所以两个goroutine可以一起被执行,以同样的频率交替打印0和1。
调用 runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值,并返回之前(上一个)的值