并发目前来看比较主流的就三种:nginx
golang的goroutine就是为了追求更高效和低开销的并发golang
定义:在go里面,每个并发执行的活动成为goroutine。缓存
详解:goroutine能够认为是轻量级的线程,与建立线程相比,建立成本和开销都很小,每一个goroutine的堆栈只有几kb(而线程则需1M),而且堆栈可根据程序的须要增加和缩小(线程的堆栈需指明和固定),因此go程序从语言层面支持了高并发。服务器
程序执行的背后:当一个程序启动的时候,只有一个goroutine来调用main函数,称它为主goroutine,新的goroutine经过go语句进行建立。多线程
package main import ( "fmt" "time" ) func HelloWorld() { fmt.Println("Hello world goroutine") } func main() { go HelloWorld() // 开启一个新的并发运行 time.Sleep(1 * time.Second) fmt.Println("main end") }
以上执行后会输出:并发
Hello world goroutine main end
须要注意的是,main执行速度很快,不加sleep,可能先执行完毕就结束了,会看不到goroutine里头的输出。异步
这也说明了一个关键点:当main函数返回时,全部的gourutine都是暴力终结的,而后程序退出。函数
package main import ( "fmt" "time" ) func DelayPrint() { for i := 1; i <= 4; i++ { time.Sleep(250 * time.Millisecond) fmt.Println(i) } } func HelloWorld() { fmt.Println("Hello world goroutine") } func main() { go DelayPrint() // 开启第一个goroutine go HelloWorld() // 开启第二个goroutine time.Sleep(2 * time.Second) fmt.Println("main function") }
输出:高并发
Hello world goroutine 1 2 3 4 main function
DelayPrint里头有sleep,那么会致使第二个goroutine堵塞或者等待吗?
答案是:no
当main程序执行go FUNC()的时候,只是简单的调用而后就当即返回了,并不关心函数里头发生的故事情节,因此不一样的goroutine直接不影响,main会继续按顺序执行语句。
工具
若是说goroutine是Go并发的执行体,那么”通道”就是他们之间的链接。
channel是goroutine之间互相通讯的工具。
具体点的说法,channel是一种通讯管道,可以把数据放入管道,也能从管道中读出数据。一个goroutine把数据放入chan,而后另一个goroutine能够从chan里面读出数据。
package main import ( "fmt" "time" ) func main() { //var ch chan int // 声明一个传递int类型的channel ch := make(chan int) // 使用内置函数make()定义一个无缓存channel go func() { var value int value = <-ch // 从channel中读取数据,若是channel以前没有写入数据,也会致使阻塞,直到channel中被写入数据为止 fmt.Println(value) }() //========= ch <- 10 // 将一个数据value写入至channel,这会致使阻塞,直到有其余goroutine从这个channel中读取数据 //========= time.Sleep(1 * time.Second) close(ch) // 关闭channel }
有没注意到关键字”阻塞“?,这个实际上是默认的channel的接收和发送,其实也有非阻塞的,请看下文。
无缓冲通道:无缓冲通道上的发送操做将会被阻塞,直到另外一个goroutine在对应的通道上执行接收操做,此时值才传送完成,两个goroutine都继续执行。
package main import ( "fmt" "time" ) var done chan bool func HelloWorld() { fmt.Println("Hello world goroutine") time.Sleep(1 * time.Second) done <- true } func main() { done = make(chan bool) // 建立一个channel go HelloWorld() <-done fmt.Println("main function end") }
输出:
Hello world goroutine main function end
因为main不会等goroutine执行结束才返回,上一个示例专门加了sleep输出为了能够看到goroutine的输出内容,那么在这里因为是阻塞的,因此无需sleep。
将代码中”done <- true”和”<-done”,去掉再执行,看看会发生啥?
package main import ( "fmt" "time" ) var done chan bool func HelloWorld() { fmt.Println("Hello world goroutine") time.Sleep(1 * time.Second) //done <- true } func main() { done = make(chan bool) // 建立一个channel go HelloWorld() //<-done fmt.Println("main function end") }
输出:
main function end
main主程序执行完打印以后就结束了
管道:通道能够用来链接goroutine,这样一个的输出是另外一个输入。这就叫作管道。
package main import ( "fmt" "time" ) var echo chan string var receive chan string // 定义goroutine 1 func Echo() { fmt.Println("enter echo function >>>>>>>>>>>>>>>> ") time.Sleep(1 * time.Second) echo <- "Echo....." fmt.Println("exit frome Echo function <<<<<<<<<<<<") } // 定义goroutine 2 func Receive() { fmt.Println("enter Receive function >>>>>>>>>>>>>") temp := <-echo // 阻塞等待echo的通道的返回 receive <- temp fmt.Println("exit frome Receive function <<<<<<<<<<<<") } func main() { fmt.Println("enter main function >>>>>>") echo = make(chan string) receive = make(chan string) go Echo() go Receive() getStr := <-receive // 接收goroutine 2的返回 fmt.Println(getStr) fmt.Println("main function end<<<<<<") }
输出:
enter main function >>>>>> enter Receive function >>>>>>>>>>>>> enter echo function >>>>>>>>>>>>>>>> exit frome Echo function <<<<<<<<<<<< exit frome Receive function <<<<<<<<<<<< Echo..... main function end<<<<<<
在这里不必定要去关闭channel,由于底层的垃圾回收机制会根据它是否能够访问来决定是否自动回收它。(这里不是根据channel是否关闭来决定的)
单向通道类型
当程序则够复杂的时候,为了代码可读性更高,拆分红一个一个的小函数是须要的。
此时go提供了单向通道的类型,来实现函数之间channel的传递。
package main import ( "fmt" "time" ) var echo chan string var receive chan string // 定义goroutine 1 func Echo(out chan<- string) { //定义输出通道类型 fmt.Println("enter echo function >>>>>>>>>>>>>>>> ") time.Sleep(1 * time.Second) echo <- "Echo....." close(out) fmt.Println("exit frome Echo function <<<<<<<<<<<<") } // 定义goroutine 2 func Receive(out chan<- string, in <-chan string) { //定义输出通道类型和输入通道类型 fmt.Println("enter Receive function >>>>>>>>>>>>>") temp := <-in // 阻塞等待echo的通道的返回 out <- temp close(out) fmt.Println("exit frome Receive function <<<<<<<<<<<<") } func main() { fmt.Println("enter main function >>>>>>") echo = make(chan string) receive = make(chan string) go Echo(echo) go Receive(receive, echo) getStr := <-receive // 接收goroutine 2的返回 fmt.Println(getStr) fmt.Println("main function end<<<<<<") }
输出:
enter main function >>>>>> enter Receive function >>>>>>>>>>>>> enter echo function >>>>>>>>>>>>>>>> exit frome Receive function <<<<<<<<<<<< exit frome Echo function <<<<<<<<<<<< Echo..... main function end<<<<<<
缓冲管道:goroutine的通道默认是是阻塞的,那么有什么办法能够缓解阻塞?答案是:加一个缓冲区。
对于go来讲建立一个缓冲通道很简单:
ch := make(chan string, 3) // 建立了缓冲区为3的通道 //========= len(ch) // 长度计算 cap(ch) // 容量计算
package main import ( "fmt" ) func f1(c chan int) { // chan int 表示参数的类型是存储int类型的chanel c <- 1 //向这个chanel中传入1,以后main()中就会接受到1 } func f2(c chan int) { // chan int 表示参数的类型是存储int类型的chanel c <- 2 //向这个chanel中传入2,以后main()中就会接收到2 } func main() { c := make(chan int, 2) //建立带有缓冲的chanel,缓冲大小是2 //这样调用函数,那么f1和f2就是并发执行了 go f1(c) //将参数c传递给f1() go f2(c) //将参数c传递给f2() c1 := <-c c2 := <-c //main函数只有从c中接收到俩个值,才会退出main(),不然main()中会阻塞这那直到c中有数据能够接收 fmt.Printf("c1:%d\nc2:%d\n", c1, c2) }
输出:
c1:2 c2:1
流出无流入
package main func main() { ch := make(chan int) <-ch // 阻塞main goroutine, 通道被锁 }
输出:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() D:/GOPATH/src/study.go/main.go:5 +0x54
流入无流出(cha)死锁现场2:
package main func main() { cha, chb := make(chan int), make(chan int) go func() { cha <- 1 // cha通道的数据没有被其余goroutine读取走,堵塞当前goroutine chb <- 0 }() <-chb // chb 等待数据的写 }
为何会有死锁的产生?
非缓冲通道上若是发生了流入无流出,或者流出无流入,就会引发死锁。
或者这么说:goroutine的非缓冲通道里头必定要一进一出,成对出现才行。
固然,有一个例外:
package main func main() { ch := make(chan int) go func() { ch <- 1 }() }
执行以上代码将会发现,居然没有报错。
why?
不是说好的一进一出就死锁吗?
仔细研究会发现,main其实根本没等goroutine执行完,main函数本身先跑完了,因此就没有数据流入主的goroutine,就不会被阻塞和报错
有两种办法能够解决:
1.把没取走的取走即是
以下:
package main func main() { cha, chb := make(chan int), make(chan int) go func() { cha <- 1 // cha通道的数据没有被其余goroutine读取走,堵塞当前goroutine chb <- 0 }() <-cha <-chb // chb 等待数据的写 }
2.建立缓冲通道
package main func main() { cha, chb := make(chan int, 3), make(chan int) go func() { cha <- 1 // cha通道的数据没有被其余goroutine读取走,堵塞当前goroutine chb <- 0 }() <-chb // chb 等待数据的写 }
这样的话,cha能够缓存一个数据,cha就不会挂起当前的goroutine了。除非再放两个进去,塞满缓冲通道就会了。
package main import ( "fmt" ) func main() { cha, chb := make(chan int, 3), make(chan int) go func() { cha <- 1 // cha通道的数据没有被其余goroutine读取走,堵塞当前goroutine chb <- 0 cha <- 1 cha <- 1 cha <- 1 fmt.Println("goroutine end") }() <-chb // chb 等待数据的写 <-cha fmt.Println("main end") }
输出:
main end
在golang里头select的功能与epoll(nginx)/poll/select的功能相似,都是坚挺IO操做,当IO操做发生的时候,触发相应的动做。
select有几个重要的点要强调:
1.若是有多个case均可以运行,select会随机公平地选出一个执行,其余不会执行
上代码:
package main import "fmt" func main() { ch := make(chan int, 1) ch <- 1 select { case <-ch: fmt.Println("select 1") case <-ch: fmt.Println("select 2") } }
输出:
select 1 和select 2 二选一
2.case后面必须是channel操做,不然报错。
package main import "fmt" func main() { ch := make(chan int, 1) ch <- 1 select { case <-ch: fmt.Println("咖啡色的羊驼") case 2: fmt.Println("黄色的羊驼") } }
输出报错:
.\main.go:11:7: 2 evaluated but not used .\main.go:11:7: select case must be receive, send or assign recv
3.select中的default子句老是可运行的。因此没有default的select才会阻塞等待事件
上代码:
package main import "fmt" func main() { ch := make(chan int, 1) // ch<-1 <= 注意这里备注了。 select { case <-ch: fmt.Println("select 1") default: fmt.Println("default") } }
输出:
default
4.没有运行的case,那么阻塞事件发生,报错(死锁)
package main import "fmt" func main() { ch := make(chan int, 1) // ch<-1 <= 注意这里备注了。 select { case <-ch: fmt.Println("select 1") } }
输出:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() D:/GOPATH/src/study.go/main.go:9 +0x5d
1.timeout 机制(超时判断)
package main import ( "fmt" "time" ) func main() { timeout := make(chan bool, 1) go func() { time.Sleep(1 * time.Second) // 休眠1s,若是超过1s还没操做则认为超时,通知select已经超时啦~ timeout <- true }() ch := make(chan int) select { case <-ch: case <-timeout: fmt.Println("Timeout!") } }
输出:
Timeout!
package main import ( "fmt" "time" ) func main() { ch := make(chan int) select { case <-ch: case <-time.After(time.Second * 1): // 利用time来实现,After表明多少时间后执行输出东西 fmt.Println("超时啦!") } }
2.判断channel是否阻塞(或者说channel是否已经满了)
package main import ( "fmt" ) func main() { ch := make(chan int, 2) // 注意这里给的容量是1 ch <- 1 ch <- 3 select { case ch <- 5: fmt.Println("1") case ch <- 6: fmt.Println("2") default: fmt.Println("通道channel已经满啦,塞不下东西了!") } }
3.退出机制
package main import ( "fmt" "time" ) func main() { i := 0 ch := make(chan string, 0) defer func() { close(ch) }() go func() { DONE: for { time.Sleep(1 * time.Second) fmt.Println(time.Now().Unix()) i++ select { case m := <-ch: println("m: ", m) break DONE // 跳出 select 和 for 循环 default: fmt.Println("default") } } }() time.Sleep(time.Second * 3) ch <- "stop" }
这边要强调一点:退出循环必定要用break + 具体的标记,或者goto也能够。不然其实不是真的退出。
package main import ( "fmt" "time" ) func main() { i := 0 ch := make(chan string, 0) defer func() { close(ch) }() go func() { for { time.Sleep(1 * time.Second) fmt.Println(time.Now().Unix()) i++ select { case m := <-ch: println(m) goto DONE // 跳出 select 和 for 循环 default: } } DONE: }() time.Sleep(time.Second * 4) ch <- "stop" }
输出:
1570846669 1570846670 1570846671 1570846672 stop
select不注意也会发生死锁,前文有提到一个,这里分几种状况,重点再次强调:
1.若是没有数据须要发送,select中又存在接收通道数据的语句,那么将发送死锁
package main func main() { ch := make(chan string) select { case <-ch: } }
预防的话加default。
package main import ( "fmt" ) func main() { ch := make(chan string) select { case <-ch: default: fmt.Println("default") } }
2.空select,也会引发死锁
package main func main() { select {} }
输出:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [select (no cases)]: main.main() D:/GOPATH/src/study.go/main.go:4 +0x27