进程和线程

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("---")}

单元测试