goroutine


goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。你将会发现,它的
使用出人意料得简单。
假设我们需要实现一个函数Add(),它把两个参数相加,并将结果打印到屏幕上,具体代码
如下:
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
那么,如何让这个函数并发执行呢?具体代码如下:
go Add(1, 1)
是不是很简单?
你应该已经猜到,“go”这个单词是关键。与普通的函数调用相比,这也是唯一的区别。的
确, go是Go语言中最重要的关键字,这一点从Go语言本身的命名即可看出。
在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用
的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个
返回值会被丢弃。


案例分析:


package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		//如果此处不加sleep则go say("world")将无打印输出
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}


Go 程序从初始化 main package 并执行 main() 函数开始,当 main() 函数返回时,程序退出,且程序并不等待其他 goroutine (非主 goroutine )结束。



channel

channel 是有类型的管道,可以用 channel 操作符 <-



ch <- v // 将 v 送入 channel ch。 v := <-ch // 从 ch 接收,并且赋值给 v。



(“箭头”就是数据流的方向。)

和 map 与 slice 一样,channel 使用前必须创建:



ch := make(chan int)



默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。


package main

import "fmt"

func sum(a []int, c chan int) {
	sum := 0
	for _, v := range a {
		sum += v
	}
	c <- sum // 将和送入 c
}

func main() {
	a := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(a[len(a)/2:], c)
	go sum(a[:len(a)/2], c)
	
	x, y := <-c, <-c // 从 c 中获取,后进先出,输出结果17  -5  12

	fmt.Println(x, y, x+y)
}

缓冲 channel

channel 可以是 带缓冲的。为 make



ch := make(chan int, 100)



向带缓冲的 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。 而当缓冲区为空的时候接收操作会阻塞。

package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
//	ch <- 3  //送入channel超出缓冲区,编译时会报错all goroutines are asleep - deadlock!
	fmt.Println(<-ch)
	fmt.Println(<-ch)
//	fmt.Println(<-ch) //从channel取出值时超出缓冲区或缓冲区为空都会报错all goroutines are asleep - deadlock!
}



range 和 close

发送者可以 close



v, ok := <-ch



之后 ok 会被设置为 false。

循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。

*注意:* 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 *还要注意:* channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range。

package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)  //此处如果不关闭channel,在下边使用range遍历到末尾时还会继续取而导致超出缓冲区,报错all goroutines are asleep - deadlock!
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

select

select

select


select {


case <-chan1:

//

如果 chan1 成功读到数据,则进行该 case 处理语句


case chan2 <- 1:

//

如果成功向 chan2 写入数据,则进行该 case 处理语句


default :

//

如果上面都没有成功,则进入 default 处理流程


}


package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}



案例二


package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}