Go是一个 并发语言: 同时处理多个事情的能力称为并发性(同一时间点只能做一件任务), 而并行性(你边跑,边听歌)。
并行不一定快: 因为并行运行组件之间需要通信的,这个通信的成本很高,而并发通信的成本就很低了。
所以 线程间通信成本远远低于进程间通信。
进程间通信(并行,成本高)
线程间通信 (并发: 成本低)
协程 又称为 轻线程
协程可以开几百万个
而线程 进程 不会超过一万个
看下 macOS 下的 进程线程
Goroutine 轻量级的 线程: 是Go的专有名词,区别于进程Process ,线程Thread , 协程 Coroutine . 因为 Go语言的创造者认为Go的协程 和 其他语言的协程是有所区别的,因此: 专门创造了Goroutine
Goroutine 主要执行 并发 : 可以理解为函数
go + 函数 () 函数没有返回值, 有返回值 也会被舍弃
package main
import "fmt"
func printNum(){
for i:=1;i <=1000;i++{
fmt.Println(i)
}
}
func printChar(){
for i:=1; i<1000; i++{
fmt.Println('A')
}
}
func main(){
//一个Goroutine打印数字, 另一个过Goroutine打印z字母:
go printNum()
for i:=1; i<1000; i++{
fmt.Printf("%c\n",'A')
}
fmt.Println("main over...")
}
注意 ; 对于Go 语言 来说 : 主Goroutine 结束了 ,那么,整个程序就结束了 ,不会管你 子Goroutine 是否执行完毕
我们可以借助 : time.sleep(1* time.Second)来进行验证
因此 : 引入了 Channel通道
1 : 当一个 新的 Goroutine开始时:Goroutine 调用立即返回,与函数不同, go程序 不等待Goroutine执行结束。 当Goroutine调用,任何返回值都会被忽略,接着 go程序 立即执行下一行代码。
2 如果主的 Goroutine执行结束,程序就会终止, 其他Goroutine 将不再运行。
封装main函数的 Goroutine 称为主的Goroutine, 主协程 并不仅仅是 执行main 函数,
1 : 首先 : 主协程 要设定每一个协程栈空间的最大值, 32位 协程栈空间的最大值位250M, 64位为1GB, 当协程的栈空间超过设定的空间时, 就会引起panic恐慌,俗称栈溢出, (依旧是异常中断),go程序终止
2 : 接着 : 主协程 进行一系列的初始化工作:
a: 设置一个 特殊的 defer 语句:用于在主协程退出之后的善后工作,因为主协程也可能非正常结束。
b: 启动专门用于在后台清扫内存垃圾的协程goroutine, 并设置GC(内存清理)可用的标识
c: 执行main包中的 init函数
d: 执行main函数
执行完main函数之后, 他还会检查主协程是否引发了运行时的panic 恐慌, 并进行必要的处理, 最后主协程会结束自己以及当前进程的运行。
goroutine 运行时会像线程一样 抢占系统资源的,抢占式执行,我们也可以 通过睡眠 控制 goroutine的执行流程。
并发模型在操作系统层面都是以线程的方式展现的。、
内核空间; 访问CPU资源, I哦资源, 内存资源等硬件资源,
用户空间: 不可直接访问资源, 必须通过系统调用,函数库、 Shell 脚本等带哦用系统资源。。
内核级线程模型、 用户级线程模型、 和 两级线程模型
最大差异: 线程 与 内核调度实体之间的 对应关系上
内核调度实体(KSE): 可以被内核调度器调度的对象。
将用户级线程 和 内核级线程1对1 的联系起来。
好处 ; 支持真正的 并行
缺点 : 线程创建开销大,影响效率
多个用户级线程 : 1个内核级线程(M:1)
缺点: 当某个用户线程上调用 阻塞式系统调用(read网络IO), 那么内核级线程可能发生阻塞, 导致所有的用户级线程全部阻塞。
使用的语言会自己封装实现阻塞式的系统调用。
混合型线程模型: 用户调度器实现用户线程到(内核调度实体)KSE的调度;而内核调度器(KSE)实现KSE到CPU上的线程调度。
在操作系统提供的那个线程之上, Go语言搭建了一个特有的两级线程模型:。 goroutine 机制实现了M:N的线程模型, goroutine机制是协程的一种实现, golang 内置的调度器可以让多核CPU中每个CPU执行一个协程
理解goroutine 机制的原理: 关键在理解 go语言 scheduler 的实现。
Go语言中支持scheduler 的实现靠4个结构,分别是:M - G - P - Sched
前三个定义在runtime.h中, Sched 定义在 proc.c中
Sched结构就是调度器, 他维护有存储M 和 G 的队列以及调度器的一些状态信息等。
**M结构是Machine ,**属于系统线程,由操作系统管理, goroutine 就是跑在M之上的, M是一个很大的结构,里面维护小对象的内存(Cache)、当前执行的Goroutine、随机数发生器等非常多的信息。
P 结构是 processor, (逻辑)处理器(上下文环境), 他的主要用途就是用来执行 goroutine的, 他维护了一个goroutine队列,即runqueue , processor 处理器 是让我们从 N:1 调度 到 M:N调度 的重要部分。
G是goroutine实现的核心结构, 它包含了栈,指令指针, 以及其他对goroutine很重要的信息, 例如 channel
在单核处理器的场景下: 所有的goroutine运行在同一个M系统线程中,每一个M系统线程维护一个P(processor),任何时刻,每一个上下文环境P中只有一个G(goroutine) 其他的gorountine 在runqueue中等待, 一个goroutine运行完自己的时间片后, 让出上下文P, 回到runqueue中,
多核处理器环境下,为了运行多个goroutine, 每个M系统线程会持有一个P.
在正常情况下,scheduler会按照上面的流程进行调度,但是线程会发生阻塞等情况
包含了 Go运行时的系统交互的操作,goroutine的控制函数, 运行时类信息等等。 内存分配, 栈的信息、 GC处理等等
fmt.Println("goroot目录:", runtime.GOROOT())
fmt.Println("当前操作系统:",runtime.GOOS)//darwin, mac系统
fmt.Println("逻辑CPU的数量-->",runtime.NumCPU())//
//注意最在程序运行之前就设置好, 可以写在init函数里
n := runtime.GOMAXPROCS(runtime.NumCPU())//设置逻辑CPU的数量,返回值是上一次设置的数量
fmt.Println("逻辑CPU的最大数量为:",n)// 说明最大只能为4
runtime.GoSched() //使当前goroutine让出CPU,先让别的goroutine执行
//goSched 让当前GoRoutine让出CPU的时间片
//一个goroutine
go func (){
for i:=0; i<4; i++ {
fmt.Println("goroutine...")
}
}()
//main goroutine
for i :=0; i<4; i++{
runtime.Gosched()//主goroutine抢到CPU时,让出时间片
fmt.Println("main。。。。")
}
但是 runtime.Gosched 本身也是可以抢占资源的 ,多执行几次就会发现:
runtime,Goexist() :终止当前的goroutine : 但是defer 语句还是会执行的
package main
func main(){
go func(){
fmt.Println("go 开始...")
fun()
fmt.Println("go程序结束")
}()
time.Sleep(3 * time.Second)//主 goroutine睡3秒
}
func fun(){
fmt.Println("进入fun函数...")
defer fmt.Println("defer...")
//return // 只是结束当前fun函数
runtime.Goexit() // 完全退出当前的goroutine
fmt.Println("退出fun函数...")
}
var count int =100
func fun1(name string) {
rand.Seed(time.Now().UnixNano())
for true{
if count > 0 {
//模拟 业务处理耗费时间
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
fmt.Println(name,": 出票成功... 当前余票: %d", count-1)
count--;
} else {
fmt.Println(name,": Sorry, 暂无余票...")
break
}
}
}
func SellTicket(){
go fun1("窗口1")
go fun1("窗口2")
go fun1("窗口3")
go fun1("窗口4")
}
func main() {
SellTicket()
time.Sleep(3 * time.Second)
}
注意: 是不是发生了临街资源不安全的问题: 在 C++下也是一样的, 会发生临街资源安全问题。
解决策略: 1 加锁 : 2 无锁编程
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup //创建同步等待组对象
func func1(){
for i:=0; i<10; i++{
fmt.Println("func1 函数打印... A ",i)
}
defer wg.Done()// 该函数执行结束时: wg同步等待组中的goroutine数量 -1
}
func func2(){
defer wg.Done()// 该函数执行结束时: wg同步等待组中的goroutine数量 -1
for i:=0; i<10; i++{
fmt.Println("\tfunc2 函数打印... ",i)
}
}
func main(){
/*
WaitGroup: 同步等待锁
Add()设置等待组中要执行的 goroutine的数量
Wait() 让主的goroutine等待
*/
wg.Add(2)// 我们目前有两个子的goroutine 要执行
go func1()
go func2()
fmt.Println(" main 函数进入阻塞状态。。等待wg同步等待组中的goroutine全部结束")
wg.Wait()// 表示 main goroutine进入等待,意味着阻塞
//当 wg中goroutine 数量为0时 ,主 goroutine解除阻塞
fmt.Println("main 接触 阻塞状态")
}
注意 wg.Add()方法 ;数量不能多, 也不能少“
多了 就会形成死锁: 导致 主goroutine无法被解除
少了 有的 goroutine 就不会被执行
想要增加 同步等待组中 goroutine的数量,必须在 Wait之前, 想要减少 ,随时都可以。
var count int =100
var wg sync.WaitGroup // 设置同步等待组对象,避免主goroutine的sleep
var mutex sync.Mutex //创建一个锁
func fun1(name string) {
defer wg.Done() //同步等待组中goroutine数量及时减1,避免形成死锁(主的goroutine 解除不了阻塞)
rand.Seed(time.Now().UnixNano())
for true{
//上锁:
mutex.Lock()
if count > 0 {
//模拟 业务处理耗费时间
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
fmt.Println(name,": 出票成功... 当前余票: %d", count-1)
count--
mutex.UnLock()
} else {
fmt.Println(name,": Sorry, 暂无余票...")
mutex.UnLock()
break
}
}
}
func SellTicket(){
go fun1("窗口1")
go fun1("窗口2")
go fun1("窗口3")
go fun1("窗口4")
}
func main() {
wg.Add(4)//数量不能多,不能少
SellTicket()
wg.Wait()//主 goroutine 进行阻塞等待,等到wg中数量为0
//time.Sleep(3 * time.Second)
fmt.Println("main over ...")
}
主要是 写锁需要同步与互斥
RLock()读锁
RUnlock()渎解锁
该方法处于接口内: 不同对象调用
如果没有上锁 ,却去解锁: 是会发生错误的
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var rwMutex *sync.RWMutex
func readMutex(i int){
defer wg.Done()
fmt.Println(i,"开始读...")
rwMutex.RLock()
fmt.Println(i, "正在读取数据...")
time.Sleep(1 * time.Second)
rwMutex.RUnlock()
fmt.Println(i, "读结束....")
}
func main(){
rwMutex = new(sync.RWMutex)
go readMutex(1)
go readMutex(2)
wg.Add(2)
wg.Wait()
fmt.Println("main over...")
}
package main
import (
"fmt"
"sync"
"time"
)
var rwMutex2 *sync.RWMutex
var wg2 sync.WaitGroup
func writeData(i int){
defer wg2.Done()
fmt.Println("开始写...")
rwMutex2.Lock()
fmt.Println(i,"正在写入...")
time.Sleep(1 * time.Second)
fmt.Println(i,"写结束...")
rwMutex2.Unlock()
}
func main(){
rwMutex2 = new(sync.RWMutex)
go writeData(1)
go writeData(2)
go writeData(3)
wg2.Add(3)
wg2.Wait()
fmt.Println("main over...")
}