golang-goroutine和channel

goroutine

在go语言中,每个并发的执行单元叫作一个goroutinehtml

这里说到并发,因此先解释一下并发和并行的概念:编程

并发:逻辑上具有同时处理多个任务的能力缓存

并行:物理上在同一时刻执行多个并发任务安全

当一个程序启动时,其主函数即在一个单独的goroutine中运行,通常这个goroutine是主goroutine网络

若是想要建立新的goroutine,只须要再执行普通函数或者方法的的前面加上关键字go并发

经过下面一个例子演示并发的效果,主goroutine会计算第45个斐波那契函数,在计算的同时会循环打印:-\|/  tcp

这里须要知道:当主goroutine结束以后,全部的goroutine都会被打断,程序就会退出函数

package main import ( "fmt"
    "time" ) func spinner(delay time.Duration) { for { for _, r := range `-\|/` { fmt.Printf("\r%c", r) time.Sleep(delay) } } } func fib(n int) int { //斐波那契数列
    if n < 2 { return n } return fib(n-1) + fib(n-2) } func main() { go spinner(100 * time.Millisecond) const n = 45 fibN := fib(n) fmt.Printf("\rFib(%d)=%d\n", n, fibN) }

当第一次看到go的并发,感受真是太好用了!!!!单元测试

因此在网络编程里,服务端都是须要同时能够处理不少个链接,咱们看一下下面的服务端和客户端例子测试

服务端:

package main import ( "io"
    "log"
    "net"
    "time" ) func handleConn(c net.Conn) { defer c.Close() _, err := io.WriteString(c, time.Now().Format("15:04:05\r\n")) if err != nil { return } time.Sleep(1 * time.Second) } func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) return } for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } go handleConn(conn) } }

客户端

package main import ( "io"
    "log"
    "net"
    "os" ) func mustCopy(dst io.Writer, src io.Reader) { // 从链接中读取内容,并写到标准输出
    if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) } } func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } mustCopy(os.Stdout, conn) }

Channel

channel是不一样的goroutine之间的通讯机制。

一个goroutine经过channel给另一个goroutine发送信息。

每一个channel 都有一个特殊的类型,也就是channel能够发送的数据的类型

咱们能够经过make建立一个channel如:

ch := make(chan int)  这就是建立了一个类型为int的channel

默认咱们这样建立的是无缓存的channel,固然咱们能够经过第二个参数来设置容量

ch := make(chan int,10)

注意:channel是引用类型,channel的零值也是nil

两个相同类型的channel可使用==运算符比较。若是两个channel引用的是相通的对象,那么比较的结
果为真。一个channel也能够和nil进行比较。 

由于channel是在不一样的goroutine之间进行通讯的,因此channel这里有两种操做:存数据和取数据,而这里两种操做的

方法都是经过运算符:<-

ch <- x  这里是发送一个值x到channel中

x = <- ch 这里是从channel获取一个值存到变量x

<-ch 这里是从channel中取出数据,可是不使用结果

close(ch) 用于关闭channel

当咱们关闭channel后,再次发送就会致使panic异常,可是若是以前发送过数据,咱们在关闭channel以后依然能够执行接收操做

若是没有数据的话,会产生一个零值

 

基于channel发送消息有两个重要方面,首先每一个消息都有一个值,可是有时候通信的事件和发送的时刻也一样重要。

咱们更但愿强调通信发送的时刻时,咱们将它称为消息事件。有些消息并不携带额外的信息,它仅仅是用作两个goroutine之间的同步,这个时候咱们能够用struct{}空结构体做为channel元素的类型

 

 

 无缓存的channel

基于无缓存的channel的发送和接受操做将致使两个goroutine作一次同步操做,因此无缓存channel有时候也被称为同步channel

串联的channel (Pipeline)

channel也能够用于多个goroutine链接在一块儿,一个channel的输出做为下一个channel的输入,这种串联的channel就是所谓的pipeline

经过下面例子理解,第一个goroutine是一个计算器,用于生成0,1,2...形式的整数序列,而后经过channel将该整数序列

发送给第二个goroutine;第二个goroutine是一个求平方的程序,对收到的每一个整数求平方,而后将平方后的结果经过第二个channel发送给第三个goroutine

第三个goroutine是一个打印程序,打印收到的每一个整数

package main import ( "fmt"
    "time" ) func main() { naturals := make(chan int) squares := make(chan int) go func() { for x := 0;; x++ { naturals <- x } }() go func() { for { x := <-naturals squares <- x * x } }() for { fmt.Println(<-squares) time.Sleep(100 * time.Millisecond) } }

可是若是我把第一个生成数的写成一个有范围的循环,这个时候程序其实会报错的。

把for x := 0;; x++改为for x := 0; x < 100; x++,报错以下:fatal error: all goroutines are asleep - deadlock!

 

因此就须要想办法让发送知道没有能够发给channel的数据了,也让接受者知道没有能够接受的数据了

这个时候就须要用到close(chan)

当一个channel被关闭后,再向该channel发送数据就会致使panic异常

当从一个已经关闭的channel中接收数据,在接收完以前发送的数据后,并不会阻塞,而会马上返回零值,因此在从channel里接受数据的时候能够多获取一个值如:

go func(){ for { x ,ok := <-naturals if !ok{ break } squares <- x*x } close(squares) }()
第二位ok是一个布尔值,true表示成功从channel接受到值,false表示channel已经被关闭而且里面没有值能够接收

 

单方向的channel

当一个channel做为一个函数的参数时,它通常老是被专门用于只发送或者只接收

chan <- int :表示一个只发送int的channel,只能发送不能接收

< chan int : 表示一个只接受int的channel,只能接收不能发送

 

固然在有时候咱们须要获取channel内部缓存的容量,能够经过内置的cap函数获取

而len函数则返回的是channel内实际有效的元素个数

基于select的多路复用

 这里先说一个拥有的知识点:time.Tick函数

这个函数返回一个channel,经过下面代码进行理解:

package main import ( "fmt"
    "time" ) func main() { tick := time.Tick(1 * time.Second) for countDown := 10; countDown > 0; countDown-- { j := <-tick fmt.Println(j) } }

程序会循环打印一个时间戳

 

select 语句:

select { case <-ch1: // ...
 case x := <-ch2: // ...use x...
 case ch3 <- y: // ...
 default: // ... 
}

select语句的形式其实和switch语句有点相似,这里每一个case表明一个通讯操做

在某个channel上发送或者接收,而且会包含一些语句组成的一个语句块 。

select中的default来设置当其它的操做都不可以立刻被处理时程序须要执行哪些逻辑

channel 的零值是nil,  而且对nil的channel 发送或者接收操做都会永远阻塞,在select语句中操做nil的channel永远都不会被select到。

这可让咱们用nil来激活或者禁用case,来达成处理其余输出或者输出时间超时和取消的逻辑

 

补充:channel 概念

相似unix中的管道pipe

先进先出

线程安全,多个goroutine同时访问,不须要加锁

channel是有类型的,一个整数的channel只能存放

定时器小例子:

//定时器
package main import ( "time"
    "fmt" ) func main() { t := time.NewTicker(time.Second) for v:= range t.C{ fmt.Println("hello",v) } }
// 一次性定时器
package main import ( "time"
    "fmt" ) func main() { select{ case <- time.After(time.Second): fmt.Println("after") } }
//超时控制
package main import (
"time" "fmt" ) func queryDb(ch chan int){ time.Sleep(time.Second) ch <- 100 } func main() { ch := make(chan int) go queryDb(ch) t := time.NewTicker(time.Second*4) select{ case v:=<-ch: fmt.Println("result:",v) case <-t.C: fmt.Println("timeout")

 

补充:不一样的goroutine之间如何通讯

首先咱们可以想到的有:全局变量的方式,咱们先经过这种本方法来演示:

package main import ( "time"
    "fmt" ) var exits [3]bool func calc(index int){ for i:=0;i<1000;i++{ time.Sleep(time.Millisecond) } exits[index] = true } func main() { start := time.Now().UnixNano() go calc(0) go calc(1) go calc(2) for{ if exits[0] && exits[1] &&exits[2]{ break } } end := time.Now().UnixNano() fmt.Println("finished,const:%d ms",end-start) }

这种方法其实比较笨,go为咱们提供了锁同步的方式 sync.WaitGroup,演示代码为:

//等待一组goroutine执行完成
 package main import ( "time"
    "fmt"
    "sync" ) var waitGroup sync.WaitGroup func calc(index int){ for i:=0;i<1000;i++{ time.Sleep(time.Millisecond) } //执行完成的时候Done
 waitGroup.Done() } func main() { start := time.Now().UnixNano() for i:=0;i<3;i++{ // 每次在调用以前add
        waitGroup.Add(1) go calc(i) } //在循环外等待wait
 waitGroup.Wait() end := time.Now().UnixNano() fmt.Println("finished,const:%d ms",end-start) }

 

补充:关于单元测试和异常捕获

package main import ( "time"
    "fmt" ) func calc(){ // defer 定义的后面出现错误的均可以捕获到
 defer func() { err := recover() if err!=nil{ fmt.Println(err) } }() var p *int
    *p = 100 }

 

转自https://www.cnblogs.com/zhaof/p/8393091.html
相关文章
相关标签/搜索