进程是拥有资源和独立运行的最小单位,也是操作系统分配资源的最小单位。
线程是程序执行的最小单位。
并发是指两个或多个事件在同一时间间隔发生,分时执行。
并行是指两个或者多个事件在同一时刻发生,同时执行。
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。
goroutine是go官方实现的超级“线程池”。每个实例4~5KB的栈内存占用和用于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因。
GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。
P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。
有效的控制goroutine数量,防止暴涨
chanel是goroutine之间连接的通道,可以将一个goroutine发送特定值到另一个goroutine的通信机制。
var ch chan int // 通道声明格式
make(chan 元素类型,[缓冲大小]) // 创建通道格式
ch <- 10 // 把10发送到通道
x := <- ch // 从通道接收值
close(ch) // 关闭通道(通道可以被垃圾回收机制回收,不必一定要关闭)
关闭通道后的特点:
1.对一个关闭的通道再发送值就会导致panic。
2.对一个关闭的通道进行接收会一直获取值直到通道为空。
3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
4.关闭一个已经关闭的通道会导致panic。
无缓冲的通道要先接收(接收会先阻塞),再发送,否则会报错。
有缓冲的通道不需要先接收,在缓存满了以后会进行等待。
Go语言在time包中提供三种定时器的使用方式:
ticker := time.NewTicker(time.Second * 1) // 运行时长
ch := make(chan int)
go func() {
var x int
for x < 10 {
select {
case <-ticker.C:
x++
fmt.Printf("%d\n", x)
}
}
ticker.Stop()
ch <- 0
}()
<-ch // 通过通道阻塞,让任务可以执行完指定的次数。
通过 time.NewTicker() 创建,这种类型,ticker会不断的按照设定的间隔时间触发,除非主动终止运行。
timer := time.NewTimer(time.Second * 1) // timer 只能按时触发一次,可通过Reset()重置后继续触发。
go func() {
var x int
for {
select {
case <-timer.C:
x++
fmt.Printf("%d,%s\n", x, time.Now().Format("2006-01-02 15:04:05"))
if x < 10 {
timer.Reset(time.Second * 2)
} else {
ch <- x
}
}
}
}()
<-ch
通过 time.NewTimer() 创建,这种类型,timer只会执行一次,当然,可以在执行完以后通过调用 timer.Reset() 让定时器再次工作,并可以更改时间间隔。
// 阻塞一下,等待主进程结束
tt := time.NewTimer(time.Second * 10)
<-tt.C
fmt.Println("over.")
<-time.After(time.Second * 4)
fmt.Println("再等待4秒退出。tt 没有终止,打印出 over 后会看见在继续执行...")
tt.Stop()
<-time.After(time.Second * 2)
fmt.Println("tt.Stop()后, tt 仍继续执行,只是关闭了 tt.C 通道。")
After()其实是Timer的一个语法糖。
select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。具体格式如下:
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
互斥锁:并发编程中对共享资源进行访问的控制手段,由标准库中的Mutex结构体类型,
sync.Mutex类型只有两个公开的指针方法,Lock和Unlock。
可以通过go build -race main.go 编译后运行, 查看共享资源的竞争。
互斥锁本质是当一个goroutine访问的时候,其他的goroutin都不能访问,
程序由原来的并行执行变成了串行执行。
读写锁:读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。
当一个goroutine进行写操作的时候,其他的goroutine不能读也不能写。
var x int64
var wg sync.WaitGroup // 计数器
var lock sync.Mutex // 互斥锁
func add() {
for i :=0;i<1000;i++{
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
sync.WaitGroup实现go并发任务的同步。
方法名 | 功能 |
---|---|
(wg * WaitGroup) Add(delta int) | 计数器+delta |
(wg *WaitGroup) Done() | 计数器-1 |
(wg *WaitGroup) Wait() | 阻塞直到计数器变为0 |
sync.Once 是 Golang package 中使方法只执行一次的对象实现,作用与 init 函数类似。但也有所不同。
sync.Map开箱即用,内置Store、Load、LoadOrStore、Delete、Range等操作方法。
Go语言中原子操作由内置的标准库sync/atomic提供。
方法 | 解释 |
---|---|
func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64 ) (val int64)func LoadUint32(addr *uint32 ) (val uint32)func LoadUint64(addr *uint64 ) (val uint64)func LoadUintptr(addr *uintptr ) (val uintptr)func LoadPointer(addr *unsafe.Pointer ) (val unsafe.Pointer) |
读取操作 |
func StoreInt32(addr *int32 , val int32) func StoreInt64(addr *int64 , val int64) func StoreUint32(addr *uint32 , val uint32) func StoreUint64(addr *uint64 , val uint64) func StoreUintptr(addr *uintptr , val uintptr) func StorePointer(addr *unsafe.Pointer , val unsafe.Pointer) |
写入操作 |
func AddInt32(addr *int32 , delta int32) (new int32) func AddInt64(addr *int64 , delta int64) (new int64) func AddUint32(addr *uint32 , delta uint32) (new uint32) func AddUint64(addr *uint64 , delta uint64) (new uint64) func AddUintptr(addr *uintptr , delta uintptr) (new uintptr) |
修改操作 |
func SwapInt32(addr *int32 , new int32) (old int32) func SwapInt64(addr *int64 , new int64) (old int64) func SwapUint32(addr *uint32 , new uint32) (old uint32) func SwapUint64(addr *uint64 , new uint64) (old uint64) func SwapUintptr(addr *uintptr , new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer , new unsafe.Pointer) (old unsafe.Pointer) |
交换操作 |
func CompareAndSwapInt32(addr *int32 , old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64 , old, new int64) (swapped bool) func CompareAndSwapUint32(addr *uint32 , old, new uint32) (swapped bool) func CompareAndSwapUint64(addr *uint64 , old, new uint64) (swapped bool) func CompareAndSwapUintptr(addr *uintptr , old, new uintptr) (swapped bool) func CompareAndSwapPointer(addr *unsafe.Pointer , old, new unsafe.Pointer) (swapped bool) |
比较并交换操作 |
M结构是Machine,系统线程,它由操作系统管理,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息
P结构是Processor,处理器,它的主要用途就是用来执行goroutine,它维护了一个 goroutine队列,即runqueue。Processor的让我们从N:1调度到M:N调度的重要部分。
G是goroutine实现的核心结构,它包含了栈,指令指针,以及其他对调度goroutine很重要的信息,例如其阻塞的channel。