[TOC]
# 简介
go为并发编程而内置上层api是基于csp(communicating sequential process, 顺序通信进程)模型.这就意味显示锁是可以避免的.因为go通过安全的通道发送和接受数据以实现同步
goroutine是go并行设计的核心,他比线程更小,十几个协程体现在底层就是5-6个线程,go帮你实现了这些协程之间的内存共享.执行协程只需极少的栈内存(大概是4-5kb),当然会根据相应的数据伸缩
~~~
return //返回当前函数
runtime.Goexit() //结束当前go程序
os.Exit(1) //当前进程结束,状态码,正常是0
~~~
![](https://box.kancloud.cn/8b78322e0dffbc9395c93a7aa7cbf26c_280x499.png)
# 关于线程
我们每运行的一个程序都会创建一个进程,每个进程都有一个初始线程,而后初始线程可以创建更多线程,每个线程互相独立地运行。线程因为其轻量和易用性在并发编程中被大量的使用。而 goroutine 就是基于线程的。线程的实现模型主要有3种:用户级线程模型、内核级线程模型和两级线程模型(或者叫做混合型线程模型)。他们之间的区别就是用户线程和系统最小调度单元内核调度实体(KSE,Kernal Scheduling Entity)的对应关系不同。
# 用户级线程模型
用户线程与内核线程KSE是多对一的映射模型,多个用户线程一般都是从属于单个进程,并且用户线程的调度都是用户程序的线程库完成的,不需要操作系统调度。一个进程所创建的所有线程都和同一个KSE绑定,操作系统调度只知道进程对线程无感知,这种模型实现的线程调度是用户层实现的不需要操作系统内核调度所以是轻量级的。但是这种模型最根本缺点就是并不能实现真正意义上的并发,因为单个进程的所有线程都是绑定在同一个KSE,各个用户线程交替执行,如果某一个用户线程阻塞了,那么整个进程就会阻塞。
# 内核级线程模型
用户线程与内核线程KSE是一对一(1:1)的映射模型,也就是每一个用户线程绑定一个真实的内核线程,那么线程调度就完全依赖于操作系统的内核调度,java语言等就是这种模式,这种模式虽然实现了真正的并行但是线程调度代价很大,严重影响系统性能。
# 两级线程模型
两级线程模型综合了上面两种模式优点,在此模型中用户线程和内核线程KSE是多对多的关系,与内核级线程模型不同的是两级线程模型中的进程可以和多个KSE绑定,而进程中的线程并不会和某一个KSE绑定,而会动态调整,当进程中的某一个线程阻塞了某一个KSE,那么进程中其余的线程会去绑定到其他的KSE。所以两级线程模型即不像用户线程模型完全靠用户调度也不像内核级线程模型完全依赖系统调度,而是一个中间态的模型。而 goroutine 就是调度器就是采用这种模型。
# G-P-M模型
golang调度系统中有3个比较重要的概念那就是G、P、M:
* G: goroutine,是 golang 调度系统中封装出来的调度单元。
* P:Processor,是 golang 调度系统中封装出的“CPU“,其提供了执行环境、内存分配状态和任务队列等。P的数量决定了 golang 系统中最大并行 goroutine 的数量。
* M 用户空间线程,同时对应一个内核线程。
# G、P、M、KSE关系
![](https://box.kancloud.cn/374b625c86dba8d173a0dc54d38c6b07_485x442.png)
根据图中2个概念需要说明:
1. Global队列,队列里是待执行的G.
2. P,每个P包含一个待执行G队列,叫做本地运行队列,队列中 G 有数量限制,最多包含256个。
当我们通过*go*关键字创建一个新的 goroutine 的时候,他会优先被放入到P的本地运行队列,如果P运行队列已满,就会放入到Global 队列中。每个M会绑定到一个 KSE ,并随机选择一个 P ,然后 M 会启动一个 OS 线程循环从绑定的P里获得 goroutine 并执行。直到P中G执行完毕,然后P会尝试从 Global 队列里获得G来执行,如果 Global 队列为空那么P会随机挑选另外一个P,从它的队列里拿来一般的G到自己的队列中执行。
**M、P的关联**
P的数量默认是CPU的数量或者 runtime.GOMAXPROCS() 来决定,在确定了P的数量后,go 运行时系统会创建相应数量的P。当P中本地运行队列不为空的时候,P会去绑定一个M,如果没有多余的M的话,那么系统回去创建一个M,go 系统会设置M最大数量限制,最大为10000。M和P没有绝对的关系,M会随机选择一个P绑定,当M阻塞的时候而P的本地运行队列不为空那么P就会去创建或者切换到一个新的M,所以即使P的默认数量是1也会创建出多个M
- 基础
- 简介
- 主要特征
- 变量和常量
- 编码转换
- 数组
- byte与rune
- big
- sort接口
- 和mysql类型对应
- 函数
- 闭包
- 工作区
- 复合类型
- 指针
- 切片
- map
- 结构体
- sync.Map
- 随机数
- 面向对象
- 匿名组合
- 方法
- 接口
- 权限
- 类型查询
- 异常处理
- error
- panic
- recover
- 自定义错误
- 字符串处理
- 正则表达式
- json
- 文件操作
- os
- 文件读写
- 目录
- bufio
- ioutil
- gob
- 栈帧的内存布局
- shell
- 时间处理
- time详情
- time使用
- new和make的区别
- container
- list
- heap
- ring
- 测试
- 单元测试
- Mock依赖
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 详解
- plugin包
- 信号
- goto
- 协程
- 简介
- 创建
- 协程退出
- runtime
- channel
- select
- 死锁
- 互斥锁
- 读写锁
- 条件变量
- 嵌套
- 计算单个协程占用内存
- 执行规则
- 原子操作
- WaitGroup
- 定时器
- 对象池
- sync.once
- 网络编程
- 分层模型
- socket
- tcp
- udp
- 服务端
- 客户端
- 并发服务器
- Http
- 简介
- http服务器
- http客户端
- 爬虫
- 平滑重启
- context
- httptest
- 优雅中止
- web服务平滑重启
- beego
- 安装
- 路由器
- orm
- 单表增删改查
- 多级表
- orm使用
- 高级查询
- 关系查询
- SQL查询
- 元数据二次定义
- 控制器
- 参数解析
- 过滤器
- 数据输出
- 表单数据验证
- 错误处理
- 日志
- 模块
- cache
- task
- 调试模块
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密码
- 对称加密
- 非对称加密
- 单向散列函数
- 消息认证
- 数字签名
- mysql优化
- 常见错误
- go run的错误
- 新手常见错误
- 中级错误
- 高级错误
- 常用工具
- 协程-泄露
- go env
- gometalinter代码检查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高编译
- go get
- 代理
- 其他的知识
- go内存对齐
- 细节总结
- nginx路由匹配
- 一些博客
- redis为什么快
- cpu高速缓存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密码加密解密
- for 循环迭代变量
- 备注
- 垃圾回收
- 协程和纤程
- tar-gz
- 红包算法
- 解决golang.org/x 下载失败
- 逃逸分析
- docker
- 镜像
- 容器
- 数据卷
- 网络管理
- 网络模式
- dockerfile
- docker-composer
- 微服务
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell调用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb