Go语言优势:并发编程。
并行和并发的区别:
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干片段,使多个进程快速交替的执行。
Go语言并发优势:从语言层面支持并发并且支持自动垃圾回收机制。顺序通信进程避免显式死锁。创建goroutine协程,比线程更小,简单,实用。
协程的创建:
package main
import "time" //可以调用语句time.Sleep(time.Second)延时一秒
func newTask(){}
func main(){
go newTask() //新建一个协程,子协程
for{} //主协程
}
主协程与子协程交替执行,任务调度器自动协调。但需要注意的是,如果主协程main先退出,子协程也会跟着退出。有事主协程退出可能导致子协程没有来得及调用。协程也可以通过go func(){}()创建给匿名函数。
协程相关runtime函数:需要导入runtime包。
runtime.Gosched()函数:用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务执行,并在下次某个时候从该位置恢复运行。
package main
import "time"
import "runtime"
func newTask(){}
func main(){
go newTask() //新建一个协程,子协程
for{ //主协程
runtime.Gosched() //让出时间片,给其他协程执行,之后完成后调回此协程执行
}
}
runtime.Foexit()函数:立即终止当前goroutine执行,调度器确保所有已经注册defer延迟调用被执行。
package main
import "time"
import "runtime"
import "fmt"
func newTask(){
defer fmt.Println("延迟调用")
//return 直接终止此函数但是不会终止协程
runtime.Goexit() //直接终止协程,在终止函数之后的所有语句都无法执行(不影响延迟语句的执行)
fmt.Println("测试语句1") //不会输出
}
func main(){
go fun(){
newTask()
fmt.Println("测试语句2") //不会输出
}
for{
runtime.Gosched() //让出时间片,给其他协程执行,之后完成后调回此协程执行
}
}
runtime.GOMAXPROCS()函数:用来设置可以并行的计算机的CPU的核数最大值,并返回之前的值。
func main(){
n := runtime.GOMAXPROCS(1) //指定单核运行,核数多可分配的时间片较多,执行较快。
}
多任务执行时存在着资源竞争问题:主协程运行时,多个子协程同时运行,代码执行较乱。
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine奉行通过通信来共享内存,而不是共享内存来通信,故引入协程同步及数据交换:channel类型。
通过channel实现同步:
package main
var ch = make(chan int) //创建一个全局channel管道
func fun1(){ //先执行fun1,再向管道内发送数据留给fun2接收
ch<-123 //向管道内发送数据
}
func fun2(){
<-ch //检测到管道内数据,接收数据并立即开始执行fun2。如果管道内没有数据则阻塞
}
func main(){
go fun1()
go fun2()
for{}
}
通过channel实现同步以及数据交互:
func main(){
ch := make(chan string)
go func(){
fmt.Println("子协程执行")
ch<-"子协程工作完毕"
}()
str:=<-ch //没有数据主协程阻塞
fmt.Println(str)
}
无缓存的channel与有缓存的channel:
无缓存的channel:管道之中不能存放数据,存在两端阻塞:接收者接受数据之前发送者一直是阻塞的,反之同理。
定义方式:
make (chan Type) //make (chan Type,0)
如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好接收。
內建函数:可以通过len()来判断缓冲区剩余的数据个数,通过cap()来查看缓冲区的大小。
有缓存的channel:管道中可以存放一定量的数据,解决两端阻塞问题,但如果数据超出存储能力也会存在阻塞。
定义方式:
make (chan Type, capacity) //capacity为int类型的容量
如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用的空间用于发送数据,或还包含可以接收的数据,那么其通信就会 无阻塞地进行。
package main
import "fmt"
var ch = make(chan int, 3) //创建一个全局channel管道
func fun1(){ //执行fun1,多次向管道内发送数不存在阻塞
for i:=0;i<3;i++{ //超过通道容量可能出现阻塞
ch<-i
}
}
func fun2(){
for i:=0;i<3;i++{ //超过通道容量可能出现阻塞
fmt.Println(<-ch) //检测到管道内数据就可以开始接收数据并执行fun2
}
}
func main(){
go fun1()
go fun2()
for{}
}
程序有缓存的channel实现了一次性在管道中存入多个数据,不阻塞。但for传递的数据如果超出容量3仍然会阻塞。
channel的关闭:
for循环检测关闭:
func main(){
ch := make(chan int)
go func(){
for i:=0;i<5;i++{
ch<-i
}
close(ch) //写入完成,关闭管道
}()
for{
if num, ok := <-ch; ok == true{
fmt.Println(num)
} else { //管道关闭
break
}
}
}
迭代检测关闭:
func main(){
ch := make(chan int)
go func(){
for i:=0;i<5;i++{
ch<-i
}
close(ch) //写入完成,关闭管道
}()
for num := range ch{ //可以自动跳出循环,更加简便
fmt.Println(num)
}
}
总结:
1.channel不像文件一样需要经常去关闭,只有当你确实没有任何发送的数据了,或者你想显式的结束range虚幻之类的,餐区关闭通道。
2.关闭channel后,无法向channel再发送数据(引发panic错误后导致接收立即返回零值)。
3.关闭channel后,可以继续向channel接收数据。
4.对于nil channel,无论收发搜会被阻塞。
单向管道与双向管道:
双向管道:能够正常进行收发
var ch1 chan int
单向管道:只能进行接收或者只能进行发送
var ch2 chan<- float64 //只能写入float64数据
var ch3 <-chan int //只能读取int数据
chan<-表示数据进入管道,要把数据写进管道,对于调用者就是输出。
<-chan表示数据从管道出来,对于调用者就是得到管道的数据,当然就是输入。
单向管道只能编译不能运行,否则会出现死锁。但双向管道可以转化为单向管道,单向管道不能转化为双向管道:
ch := make(chan int)
var writeCh chan<- int = ch //只能写不能读
var readCh <-chan int = ch //只能读不能写
单向channel作为函数参数的应用:引用传递
func producer(out chan<- int) {
for i:=1;i<10;i++ {
out <- i*i
}
close(out)
}
func consumer(in <-chan int) {
for num := range in{
fmt.Println(num)
}
}
func main(){
ch := make(chan int)
go producer(ch) //引用传递
consumer(ch) //引用传递
}
定时器:
Timer定时:Timer是一个定时器,代表未来的一个单一事件(只会产生一次事件),你可以告诉timer你要等待多长时间,它提供一个channel,在将来的那个时间的channel提供了一个时间值。需要导入包time
package main
import "time"
import "fmt"
func main(){
//创建一个定时器延时两秒,两面后向time通道写内容(当前时间)
timer := time.NowTimer(2 * time.Second) //延时2秒
fmt.Println("当前时间:",time.Now())
//2s后,向time.C写数据,检测到数据后可以进行读取
t := <-timer.C //没有数据则会阻塞
fmt.Println(t)
}
三种方式通过timer进行延时:
func mian(){
<-timer.After(2 * time.Second) //定时2s阻塞2s,之后产生事件
fmt.Println("时间到")
}
func main(){
time.Sleep(2 * time.Second)
fmt.Println("时间到")
}
func main(){
timer := time.NewTimer(2 * time.Second)
<-time.C
fmt.Println("时间到")
}
定时器的停止:
func main(){
timer := time.NewTimer(3 * time.Second)
go func(){
<-time.C
fmt.Println("时间到") //不会输出
}
timer.Stop() //计时器被停止,通道不会被写入内容
for{}
}
计时器的重置:
func main(){
timer := time.NewTimer(3 * time.Second)
timer.Reset(1 * time.Second) //被更改为延时一秒,语句返回bool类型设置状态
<-time.C
fmt.Println("时间到") //程序运行一秒后输出
}
Ticker定时:Ticker是一个定时出发的计时器,它会以一个间隔(interval)向一个通道内发送事件(当前时间)(循环产生事件),而channel的接收者可以以固定的事件间隔从channel中读取时间。需要导入time包
package main
import "time"
import "fmt"
func main(){
ticker := time.NowTicker(1 * time.Second)
i := 0
for {
<-ticker.C
i++
fmt.Println(i)
if i==5 {
ticker.Stop()
break
}
}
}
select监听:
Go语言中提供的select关键字可以箭筒channel上的数据流动。select的用法与switch语言非常相似,由select开始一个新的选择块,每个选择条件由case语句来描述。与switch语句可以选择任何可使用相等比较的条件相比,select有比较多的限制,其中最大的一条限制就是每个case与距离必须是一个IO操作,大致如下:
select {
case <-chan1:
//如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
//如果成功向chan2写入数据,则进行该case处理语句
default:
//如果上面都没有成功,则进入default处理流程,但可能使程序由阻塞变为不阻塞,故一般default不写在select语块内
}
在一select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。
如果其中的任意一个语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能情况:
1.如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。
2.如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去。
用select实现斐波那契额数列:
package main
import "fmt"
//ch只写,quit只读
func fibonacci(ch chan<- int, quit <-chan bool) {
x, y := 1, 1
for {
//监听channel数据的流动
select {
case ch <- x:
x, y = y, x+y //斐波那契数列核心推导
case flag := <-quit:
fmt.Println("flag = ", flag)
return
}
}
}
func main() {
ch := make(chan int) //数字通信
quit := make(chan bool) //程序是否结束
//消费者,从channel读取内容
//新建协程
go func() {
for i := 0; i < 8; i++ {
num := <-ch
fmt.Println(num)
}
//可以停止
quit <- true
}()
//生产者,产生数字写入channel
fibonacci(ch, quit)
}
select超时:有时候会出现goroutine阻塞的情况,那么我们可以利用select来设置超时,避免整个程序进入阻塞的状态。--长期阻塞所做的相应操作。
package main
import "fmt"
import "time"
func main() {
ch := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case num := <-ch:
fmt.Println("num = ", num)
case <-time.After(3 * time.Second):
fmt.Println("超时")
quit <- true
}
}
}()
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit
fmt.Println("程序结束")
}