一、并发编程
1.1 基础
1.1.1 进程、线程、协程
- 进程
- 进程是程序在操作系统中的一次执行过程,系统进行分配和调度的一个独立单位
- 线程
- 线程是进程的一个执行实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位
- 协程
- 独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的
1.1.2 并发和并行
- 并发
- 多线程程序在一个核的cpu上运行,就是并发
- 并行
- 多线程程序在多个核的cpu上运行,就是并行
1.1.3 注意
- Go语言的并发模型是CSP,提倡通过通信共享内存而不是通过共享内存而实现通信
1.2 goroutine
goroutine是由Go的运行时(runtime)调度和管理的,Go程序会智能地将goroutine中的任务合理地分配给每个CPU
1.2.1 使用
- 只需要在调用函数的时候再前面加上go关键字,就可以为一个函数创建一个goroutine
- 示例
- go xxx()// 启动另外一个goroutine去执行xxx函数
- 启动一个goroutine
- 启动多个goroutine
// 用sync.WaitGroup实现goroutine的同步 var wg sync.WaitGroup func hello(i int) { defer wg.Done() // goroutine结束就登记-1 fmt.Println("Hello Goroutine!", i) } func main() { for i := 0; i < 10; i++ { wg.Add(1) // 启动一个goroutine就登记+1 go hello(i) } wg.Wait() // 等待所有登记的goroutine都结束 }
1.2.2 goroutine调度
GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。
1.2.2.1 GMP
- GMP模型
- 各单元含义
- G
- 就是goroutine,里面除了存放本goroutine信息外,还有与所有P的绑定等信息
- P
- 管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针、堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等),当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了回去其它P的队列里抢任务
- M
- M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟,M与内核线程一般是一一映射的关系,一个goroutine最终是要放到M上执行的。
- 调度器的设计策略
- 核心
- 复用线程:避免频繁的创建、销毁线程,而是对线程的复用
- 机制
- 当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行
- 当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程
- work stealing机制
- hand off机制
- 详细解释
- P和M的个数
- P的数量
- 由启动时环境变量 GOMAXPROCS 个 goroutine 在同时运行。
- M的数量
- go语言本身的限制:go程序启动时,会设置M的最大数量,默认10000,但是内核很难支持这么多的线程数,所以这个限制可以忽略。
- runtime/debug中的SetMaxThreads函数,设置M的最大数量
- 一个M阻塞了,会创建新的M
- P和M何时创建
- P创建
- 在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P
- M创建
- 没有足够的M来关联P并运行其中的可运行的G,比如所有的M此时都阻塞住了,而P中海油很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M
1.2.2.2 go func()调度流程
1.2.2.3 注意
- 协程与线程之间M:N的映射关系
1.2.3 注意
- 主协程退出后,其它协程也会退出
- 可增长的栈
- OS(操作系统)线程一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况是2KB),goroutine的栈不是固定的,可以按需增大和缩小,goroutine的栈大小限制可以达到1GB。
- 在go中,线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上
- 在go中,一个goroutine最多占用CPU 10ms,防止其它goroutine被饿死
- 可以自己建立goroutine池,来有效控制goroutine数量
1.2.4 特点
- 占用内存更小(几kb)
- 调度更灵活(runtime调度)
1.3 并发安全和锁
1.3.1 背景
在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这个时候会发生竞态问题,所以需要保证其安全。
1.3.2 锁类型
- 互斥锁
- 互斥锁是一种常用的控制共享资源访问的方法,能够保证同时只有一个goroutine可以访问共享资源。
- 使用
// go语言使用sync包的Mutex类型来实现互斥锁 var lock sync.Mutex // 定义变量 lock.Lock() // 加锁 lock.Unlock() // 解锁
- 读写互斥锁
- 读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其它的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其它的goroutine无论是获取读锁还是写锁都会等待
- 使用
// go语言使用sync包中的RWMutex类型来实现读写锁 var rwlock sync.RWMutex // 定义锁变量 rwlock.Lock() // 加写锁 rwlock.Unlock() // 解写锁 rwlock.RLock() // 加读锁 rwlock.RUnlock() // 解读锁
二、常用标准库
2.1 runtime
2.1.1 功能
提供和go运行时环境的互操作
2.1.2 常用
- runtime.Gosched()
使当前go协程放弃处理器,以让其它go协程运行,当前go协程未来会恢复执行
- runtime.Goexit()
终止调用它的go协程,其他go协程不会受影响,Goexit()会在终止该go协程前执行所有defer的函数
- runtime.GOMAXPROCS(n int)
确定需要使用多少个OS线程来同时执行Go代码,默认值是机器上的CPU核心数。
2.2 time
2.2.1 功能
提供了时间的显示和测量用的函数
2.2.2 常用
- Time相关
(1)func Now() Time
功能:获取当前时间
常用
now := time.Now() // 获取当前时间 year := now.Year() // 获取年 month := now.Month() // 获取月 day := now.Day() // 获取日 hour := now.Hour() // 获取小时 minute := now.Minute() // 获取分钟 second := now.Second() // 获取秒 timestamp1 := now.Unix() // 获取时间戳 timestamp2 := now.UnixNano() // 获取纳秒时间戳
(2)func Unix(sec int64, nsec int64) Time
创建一个本地时间,例如time.Unix(timestamp, 0) // 将某个时间戳转换为时间格式;sec和nsec表示的Unix时间(从January 1, 1970 UTC至该时间的秒数和纳秒数)
(3)func (t Time) Equal(u Time) bool
判断两个时间是否相同(会考虑时区的影响)
(4) func (t Time) Add(d Duration) Time
返回时间点t+d
(5)func (t Time) Sub(u Time) Duration
返回一个时间段t-u
(6)func (t Time) Before(u Time) bool
如果t代表的时间点在u之前,返回真;否则返回假。
(7)func (t Time) After(u Time) bool
如果t代表的时间点在u之后,返回真;否则返回假。
(8) ……
- time包中定义的时间间隔Duration
time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。
常量
(1)time.Nanosecond或time.Duration // 1纳秒
(2)time.Microsecond // 1微妙
(3)time.Millsecond // 1毫秒
(4)time.Second // 1秒
(5)time.Minute // 1分钟
(6)time.Hour // 1小时
- Timer相关
Timer类型表示单次时间事件,当Timer到期时,当时的时间会被发送给C,除非Timer是被AfterFunc函数创建的
操作
(1)func NewTimer(d Duration) *Timer
NewTimer创建一个Timer,它会在最少过去时间段d后到期(时间到了,执行只执行1次)
(2)unc AfterFunc(d Duration, f func()) *Timer
AfterFunc另起一个go程等待时间段d过去,然后调用f
(3)func (t *Timer) Reset(d Duration) bool
Reset使t重新开始计时,(本方法返回后再)等待时间段d过去后到期。如果调用时t还在等待中会返回真;如果t已经到期或者被停止了会返回假。
(4)func (t *Timer) Stop() bool
Stop停止Timer的执行。
- Ticker相关
Ticker保管一个通道,并每隔一段时间向其传递"tick"。
操作
(1)func NewTicker(d Duration) *Ticker
NewTicker返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间(时间到了会多次执行)
(2)func (t *Ticker) Stop()
top关闭一个Ticker。在关闭后,将不会发送更多的tick信息。
- 其它
(1)func Sleep(d Duration)
Sleep阻塞当前go程至少d代表的时间段。d<=0时,Sleep会立刻返回。
(2)func After(d Duration) <-chan Time
After会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C。
(3)func Tick(d Duration) <-chan Time
Tick是NewTicker的封装,只提供对Ticker的通道的访问。如果不需要关闭Ticker,本函数就很方便。
2.3 sync
2.3.1 功能
sync包提供了基本的同步单元,如互斥锁
2.3.2 常用
- type Mutex
- 互斥锁
- type RWMutex
- 读写互斥锁
- type Once
Once是只执行一次动作的对象。
- func (o *Once) Do(f func())// Do方法当且仅当第一次被调用时才执行函数f。
- type WaitGroup
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。- 方法
(1)func (wg *WaitGroup) Add(delta int)
Add方法向内部计数加上delta
(2)func (wg *WaitGroup) Done()
Done方法减少WaitGroup计数器的值,应在线程的最后执行。
(3)func (wg *WaitGroup) Wait()
Wait方法阻塞直到WaitGroup计数器减为0。
(4)例子
var wg sync.WaitGroup func hello() { defer wg.Done() fmt.Println("Hello Goroutine!") } func main() { wg.Add(1) go hello() // 启动另外一个goroutine去执行hello函数 fmt.Println("main goroutine done!") wg.Wait() }
2.4 fmt
2.4.1 功能
fmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分
2.4.2 常用
2.4.2.1 向外输出
- Print系列
Print系列函数会将内容输出到系统的标准输出
(1)func Print(a ...interface{}) (n int, err error)
直接输出内容
(2)func Printf(format string, a ...interface{}) (n int, err error)
支持格式化输出字符串
(3)func Println(a ...interface{}) (n int, err error)
会在输出内容的结尾添加一个换行符
- Fprint系列
Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中,通常用这个函数往文件中写内容
(1)func Fprint(w io.Writer, a ...interface{}) (n int, err error) (2)func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) (3)func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
- Sprint系列
Sprint系列函数会把传入的数据生成并返回一个字符串
(1)func Sprint(a ...interface{}) string (2)func Sprintf(format string, a ...interface{}) string (3)func Sprintln(a ...interface{}) string
- func Errorf(format string, a ...interface{}) error
Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。
2.4.2.2 获取输入
- Scan系列
(1)func Scan(a ...interface{}) (n int, err error)
Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
(2)func Scanf(format string, a ...interface{}) (n int, err error)
Scanf从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。
(3)func Scanln(a ...interface{}) (n int, err error)
Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。
- Fscan系列
(1)func Fscan(r io.Reader, a ...interface{}) (n int, err error)
Fscan从r扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数。
(2)func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
Fscanln类似Fscan,但会在换行时才停止扫描。
(3)func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
Fscanf从r扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。
- Sscan系列
(1)func Sscan(str string, a ...interface{}) (n int, err error)
Sscan从字符串str扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数。
(2)func Sscanln(str string, a ...interface{}) (n int, err error)
Sscanln类似Sscan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置。
(3)func Sscanf(str string, format string, a ...interface{}) (n int, err error)
Sscanf从字符串str扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。
2.4.3 格式化占位符
- 通用占位符
- %v 值的默认格式表示 - %+v 类似%v,但输出结构体时会添加字段名 - %#v 值的Go语法表示 - %T 打印值的类型 - %% 百分号
- 布尔型
- %t true或false
- 整形
- %b 表示为二进制 - %c 该值对应的unicode码值 - %d 表示为十进制 - %o 表示为八进制 - %x 表示为十六进制,使用a-f - %X 表示为十六进制,使用A-F - %U 表示为Unicode格式:U+1234,等价于”U+%04X” - %q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
- 浮点数和复数
- %b 无小数部分、二进制指数的科学计数法,如-123456p-78 - %e 科学计数法,如-1234.456e+78 - %E 科学计数法,如-1234.456E+78 - %f 有小数部分但无指数部分,如123.456 - %F 等价于%f - %g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出) - %G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
- 字符串和[]byte
- %s 直接输出字符串或者[]byte - %q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示 - %x 每个字节用两字符十六进制数表示(使用a-f - %X 每个字节用两字符十六进制数表示(使用A-F
- 指针
- %p 表示为十六进制,并加上前导的0x
- 宽度标识符
- %f 默认宽度,默认精度 - %9f 宽度9,默认精度 - %.2f 默认宽度,精度2 - %9.2f 宽度9,精度2 - %9.f 宽度9,精度0
- 其它flag
- ’+’ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义); - ’ ‘ 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格 - ’-’ 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐); - ’#’ 八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值; - ‘0’ 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;
2.5 bufio
- bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
2.6 bytes
- bytes包实现了操作[]byte的常用函数。
2.7 compress系列
- 一系列与压缩相关的内容
2.8 crypto系列
- 一些列加解密相关内容
2.9 encoding系列
- encoding包定义了供其它包使用的可以将数据在字节水平和文本表示之间转换的接口
2.10 flag
- flag包实现了命令行参数的解析
2.11 io
- io包提供了对I/O原语的基本接口。本包的基本任务是包装这些原语已有的实现(如os包里的原语),使之成为共享的公共接口,这些公共接口抽象出了泛用的函数并附加了一些相关的原语的操作。
2.12 log
- log包实现了简单的日志服务。
2.13 math
- math包提供了基本的数学常数和数学函数。
2.14 net系列
- 提供了网络通讯相关的内容
2.15 os
- os包提供了操作系统函数的不依赖平台的接口,例如可进行文件操作。
2.16 reflect
- reflect包实现了运行时反射,允许程序操作任意类型的对象。
2.17 strings
- strings包实现了用于操作字符的简单函数。
2.18 其它
库太多了,可直接查文档http://doc.golang.ltd/