Golang中的goroutine
- 进程和线程说明
- 并发和并行
- 并发
- 并行
- Go协程和Go主线程
- 案例
- 小结
- goroutine的调度机制
- MPG模式基本介绍
- MPG模式运行的状态1
- MPG模式运行的状态2
- 设置GOlang运行的CPU数
- 不同 goroutine之间如何通讯
- 使用全局变量加锁同步改进程序
进程和线程说明
- 1.进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
- 2.线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
- 3.一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行
- 4.一个程序知道有一个进程,一个进程至少有一个线程
并发和并行
1、多线程程序在单核上运行,就是并发
2、多线程程序在多核上运行,就是并行
并发
因为是在CPU上,比如都有10个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观上看,在某一个时间点看,其实只有一个线程在执行,这就是并发
并行
因为是在多个CPU上(比如有10个CPU),比如有10个线程,每个线程执行10毫秒(各自在不同的CPU上执行),从人的角度看,这10个线程都在运行,并且从微观上看,在某一个时间点看,也同时有10个线程在执行,这就是并行
Go协程和Go主线程
1.Go主线程(有程序员直接称为线程/也可以理解成进程):一个Go线程上,可以起多个协程,可以理解为协程是轻量级的线程
2.Go协程的特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程
案例
编写一个程序
1、在主线程中(也可以理解成进程)中,开启一个goroutine,改协程每隔1秒输出“hello,world”
2、在主线程中也每隔1秒“hello,golang”,输出10次后,退出程序
3、要求主线程和goroutine同时执行
4、画出主线程和协程执行流程图
package main
import (
"fmt"
"strconv"
"time"
)
func test() {
for i := 1; i <= 10; i++ {
fmt.Println("test () hello,world" + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main() {
go test() //开启了一个协程
for i := 0; i <= 10; i++ {
fmt.Println("main() hello,golang" + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
/*
main() hello,golang0
test () hello,world1
test () hello,world2
main() hello,golang1
main() hello,golang2
test () hello,world3
test () hello,world4
main() hello,golang3
main() hello,golang4
test () hello,world5
test () hello,world6
main() hello,golang5
main() hello,golang6
test () hello,world7
test () hello,world8
main() hello,golang7
main() hello,golang8
test () hello,world9
main() hello,golang9
test () hello,world10
main() hello,golang10
*/
小结
- 1、主线程是一个物理线程,直接作用在CPU上的。是重量级的,非常消耗CPU资源
- 2、协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小
- 3、Golang的协程机制时重要的特点,可以轻松的开启上万个协程。其他编程语言的并发机制时一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang在并发上的优势了
goroutine的调度机制
MPG模式基本介绍
MPG模式运行的状态1
- 1、当前程序有三个M,如果三个M都在一个CPU运行,就是并发,如果在不同的CPU运行就会并行
- 2、M1,M2,M3正在执行一个G,M1的协程队列有三个,M2的协程队列有三个,M3的协程队列有两个
- 3、从下图可以看到:Go的协程是轻量级的线程,是逻辑态的,Go可以容易的起上万个协程
- 4、其他程序c/java的多线程,往往是内核态的,比较重量级,几千个线程可能耗光CPU
MPG模式运行的状态2
- 1、分两部分来看
- 2、原来的情况是M0主线程正在执行Go协程,另外有三个协程在队列等待
- 3、如果Go协程阻塞,比如读取文件或者数据库等
- 4、这时就会创建M1主线程(也可能是从已有的线程池中取出M1),并且将等待的3个协程挂到M1下开始执行,M0的主线程下的Go任然执行文件io的读写
- 5、这样的MPG调度模式,可以既让GO执行,同时不会让队列的其他协程一直阻塞,任然可以并发/并行执行
- 6、等到GO不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中取),同时GO又会被唤醒
设置GOlang运行的CPU数
介绍:为了充分利用多CPU的优势,在golang中,设置运行的CPU数目
1.go1.8后默认让程序运行在多个核上,可以不用设置了
2.go1.8前,要设置以下,可以更高效的利用CPU
package main
import (
"fmt"
"runtime"
)
func main() {
//获取当前系统CPU数量
num := runtime.NumCPU()
//这里设置num-1的CPU运行go程序
runtime.GOMAXPROCS(num)
fmt.Println("num=", num)
}
//num=8
不同 goroutine之间如何通讯
1、全局变量加锁同步
2、channel
使用全局变量加锁同步改进程序
- 因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提升concurrent map writes
- 解决方案:加入互斥锁
- 数的阶乘很大,结果会越界,可以将求阶乘改成sum += uint64(i)