Go(8[Goroutine | Channel | 读写锁 | 异常处理 ])
进程和线程
1. 进程是程序在操作系统中的⼀次执⾏过程,系统进口资源分配和调度的一个独力单位。
2. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独力运行的基本单位。
3. 一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行
Goroutine
并发和并行
多线程程序在一个核的CPU上运行,就是并发
多线程程序在多个核的CPU上运行,就是并发
设置GO运行的CPU核数
funcTest1(){num:=runtime.NumCPU()//设置程序运行占几个核runtime.GOMAXPROCS(num)fmt.Println(num)}
线程同步
sync.WaitGroup
packagemainimport("fmt""time""sync")varwailtGroupsync.WaitGroup//sync线程同步funcTest2(indexint){fori:=0;i<100;i++{time.Sleep(time.Millisecond)}wailtGroup.Done()}funccalc(){start:=time.Now().UnixNano()//Test2(0)fori:=0;i<3;i++{goTest2(i)wailtGroup.Add(1)}#当wailtGroup为0时,就会返回wailtGroup.Wait()end:=time.Now().UnixNano()fmt.Printf("finished,cost:%dms\n",(end-start)/1000/1000)}funcmain(){//Test1()calc()}
不同goroutine之间通信
全局变量和锁同步
Channel
Channel
类似unix中的管道(pipe)
先进先出
线程安全,多个goroutine同时访问,不需要加锁
Channel是有类型的,一个整数的channel只能存放整数
Channel声明:
var 变量名 chan 类型
var test chan int
var test chan string
var test chan map[string]string
var test chan stu
var test chan *stu
Channel初始化:
不带缓冲区:默认就是0,必须有取值,才可以放入,不然就会阻塞!
带缓冲区: 值>0
使用make进行初始化
var test chan int
test =make(chan int,10)
var test2 chan string
test = make(chan string,10)
Channel基本操作:
1.从channel读取数据
testChan:=make(chanint,10)varainta=<-testChan
2.从channel写数据
testChan:=make(chanint,10)varaint=10testChan<-a
栗子:
1.初级操作
functest(){varintChanchanint=make(chanint,3)gofunc(){fmt.Println("begininputtochan\n")intChan<-20intChan<-20intChan<-20fmt.Println("endinputtochan\n")}()result:=<-intChanfmt.Printf("--result:%d",result)time.Sleep(2*time.Second)}funcmain(){test()}
2.goroutine和channel结合
packagemainimport("fmt""time")funcmain(){ch:=make(chanstring)gosendData(ch)gogetData(ch)//加time原因是让2个go去执行,因为主线程中的代码是比goroutine先执行完毕的time.Sleep(100*time.Second)}funcsendData(chchanstring){ch<-"Washington"ch<-"Tripoli"ch<-"London"ch<-"Beijing"ch<-"Tokio"}funcgetData(chchanstring){varinputstringfor{input=<-chfmt.Println(input)}}
for循环去遍历chan
packagemainimport("fmt""time")funcmain(){ch:=make(chanstring)gosendData(ch)gogetData(ch)time.Sleep(100*time.Second)}funcsendData(chchanstring){ch<-"Washington"ch<-"Tripoli"ch<-"London"ch<-"Beijing"ch<-"Tokio"}funcgetData(chchanstring){forinput:=rangech{fmt.Println(input)}}
Channel关闭
使用内置函数close进行关闭,chan关闭后。for range遍历chan中已经存在的元素后结束
funcrangegetData(chchanstring){//另外用法,用来取管道中的数据//forinput:=rangech{fmt.Printf("#%v\n",input)}}
使用内置函数close进行关闭,chan关闭后。没有使用for range的写法需要使用,v,ok := <-ch 进行判断chan是否关闭
funcgetData(chchanstring){//死循环for{input,ok:=<-ch//ok是判断管道是否关闭if!ok{fmt.Printf("管道关闭")break}fmt.Printf("channe_read:%s\n",input)}}
进阶栗子:
funcconsumer(ch<-chanstring){for{str,ok:=<-chif!ok{fmt.Printf("chisclosed!!")break}fmt.Printf("valueis%s\n",str)}}funcmain(){varchchanstring=make(chanstring)consumer(ch)}
Channel只读/只写
只读chan声明
var变量名字<-chanintvarreadChan<-chanint
只写chan声明
var变量名字chan<-intvarwriteChanchan<-int
Channel Select管理
注意:调度是随机的
一个简单的栗子:
for{str:=fmt.Sprintf("hello%d",i)//select来管理管道//调度是随机的,select{casech<-str:caseexit=<-exitChan:}ifexit{fmt.Printf("usernotifyexitedd!!\n")break}}
定时器
规定时间后运行代码
packagemainimport("time""fmt")funcrun(){t:=time.NewTicker(time.Second*5)//t.C是一个管道forv:=ranget.C{fmt.Println("hello",v)}}funcmain(){run()}
只运行一次:
packagemainimport("fmt""time")funcmain(){select{case<-time.After(time.Second):fmt.Println("after")}}
超时控制 (可以用于检测类似Mysql查询超时等):
packagemainimport("time""fmt")funcqueryDB(chchanint){time.Sleep(time.Second*1000)ch<-100}funcmain(){ch:=make(chanint,5)goqueryDB(ch)//设置主线程运行时间,t:=time.NewTicker(time.Second*5)//随机调度select{//去ch中取,是否有数据,case<-ch:fmt.Println("reslt")case<-t.C:fmt.Printf("timeout!!!")}}
读写锁:
写锁:sync.Mutex 是互斥锁, 多个线程修改数据的适合,只有一个线程可以修改
读锁:sync.RWMutex 读锁,多个线程同时读一份数据,可以并发的去执行
Go中使用recover
应⽤场景,如果某个goroutine panic了,⽽且这个goroutine⾥⾯没有捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产⽣⼀个goroutine,就需要写下recover
packagemainimport("time""fmt")funccalc(){//捕获异常//必须写在最前面deferfunc(){error:=recover()iferror!=nil{fmt.Println(error)}}()varp*int//p=new(int)*p=100}funcmain(){gocalc()time.Sleep(time.Second*2)fmt.Println("---")}
单元测试
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。