goroutine
go语言并发: 能让某个函数独立于其他函数运行的能力
当一个函数创建为goroutine时,go将它视为一个独立的工作单元,这个工作单元会被调度到可用的逻辑处理器上执行
go语言运行时调度器: 能管理被创建的所有goroutine并为其分配执行时间,
调度器在操作系统之上,将操作系统的线程和语言运行时的逻辑处理器绑定,并在逻辑处理器上运行goroutine
go语言的并发同步模型: 叫通信顺序进程(communicating Sequential Processes)CSP的范性,CSP是一种消息传递模型,通过在goroutine之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问.
用于在goroutine之间同步和传递数据的关键数据类型叫做通道(channel)
并行: 是让不同的代码片段同时在不同的物理处理器上执行,关键是同时做很多事情
并发: 是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情
下图是一个逻辑处理器上并发运行goroutine和两个逻辑处理器上并行运行两个并发的goroutine
进程: 包含了应用程序运行需要用到和维护的各种资源的容器,这些资源包括但不限于内存地址空间,文件和设备的句柄以及线程
线程:一个执行空间,这个空间被操作系统调度来运行函数中所写的代码
每个进程至少包含一个线程
操作系统会在物理处理器上调度线程来运行
go语言运行时会在逻辑处理器上调度goroutine来运行
每个逻辑处理器都分别绑定在单个操作系统线程上
如果创建一个goroutine并准备运行:
这个goroutine就会被放在调度器的全局运行队列中;
调度器就将这些队列中的goroutine分配一个逻辑处理器,并放在这个逻辑处理对应的本地运行队列中;
本地运行队列中goroutine一直等待自己被分配到逻辑处理器上执行
正在运行的goroutine执行一个阻塞的系统调用:
线程和goroutine会从逻辑处理器上分离;
该线程继续阻塞等待系统调用的返回;
这个逻辑处理器就失去了运行的线程,调度器会新建线程,并将其绑定到该逻辑处理器上;
调度器会从本地运行队列中选择另外的goroutine来运行;
一旦被阻塞的系统调用执行完成并返回,对应的goroutine会放回到本地运行队列,之前的线程会保存好以遍之后使用
如果一个goroutine需要做一个网络I/O调用:
goroutine会和逻辑处理器分离,并移到集成了网络轮询器的运行时;
一旦该轮询器指示某个网络读或者写操作已经就绪,对应的gorouotine会重新分配搭配到逻辑处理器上来完成操作.
调度器对可以运行的逻辑处理器没有限制
语言运行时默认限制每个程序最多创建10000个线程,此限制可以通过调用runtime/debug包中的SetMaxThread方法来更改,如果程序试图使用更多的线程就会崩溃
创建两个goroutine,以并发的形式分别显示大写和小写的英文字母
func main(){
runtime.GOMAXPROCS(1) //分配一个逻辑处理器给调度器使用
var wg sync.WaitGroup
wg.Add(2). //计数器加2,表示要等待两个goroutine
fmt.println("Start Goroutine")
go func(){
defer wg.Done()
for i:=0;i<3;i++{
for char:='a';char<'a'+26;char++{
fmt.printf("%c",char)
}
}
}()
go func(){
defer wg.Done() //通知main函数工作已完成
for i:=0;i<3;i++{
for char:='A';char<'A'+26;char++{
fmt.printf("%c",char)
}
}
}()
fmt.println("Waiting To Finish")
wg.wait()
}
WaitGroup:是一个计数信号量,可以用来记录并维护运行的goroutine.
如果waitgroup大于0,wait方法就会阻塞
defer:会修改函数调用时机,在正在执行的函数返回时才真正调用defer声明的函数
只有在有多个逻辑处理器,且可以同时让每个goroutine运行在一个可用的物理处理器上时,goroutine才会并行运行.
竞争状态
两个或多个goroutine试图访问同一个资源
手动让一个goroutine从线程退出并放回队列的方法
runtime.Gosched()
检测竞争状态方法: 用竞争检测器标志来编译程序
go build -race
防止出现竞争状态:
1、原子函数以很低层的加锁机制来同步访问整形变量和指针
atomic.AddInt64()
atomic.StoreInt64()
atomic.LoadInt64()
2、互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行临界区代码;
sync.Mutex, mutex.Lock(),mutex.UnLock()
3、通道,通过发送和接收需要共享的资源,在goroutine之间做同步
通道
通道提供了一种在两个goroutine之间共享数据的简单方法
无缓冲通道: 接收前没有能力保存任何值的通道,要求发送gproutine和接收goroutine同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待
有缓冲通道: 被接收前能缓冲多个值的通道,此通道并不要求goroutine同事完成发送和接收
阻塞发送的条件:只有在通道中没有缓冲区容纳发送的值时,发送动作才会阻塞
阻塞接收的条件:只有在通道中没有要接收的值时,接收动作才会阻塞
所以无缓冲的通道保证同时交换数据,而有缓冲的通道不做这种保证
有缓冲通道:当通道关闭后,goroutine依旧可以从通道接收数据,不能再向通道发送数据,从已关闭通道接收数据很重要,因为这允许通道关后依旧能取出其中缓存的全部值,而不会丢失数据;
从一个已关闭但没有数据的通道里获取数据,总会立即返回,并返回一个通道类型的零值