Golang code walk: share memory by communicating

Go example code中的 urlpoll.go

实现在这里.

Go interfaces

Channel

Example usage here. Channel is a FIFO queue, the send and receive sequence will always be the same.

1
ch := make(chan int, 100)  // constructor of a Channel with capacity of 100

如果没有设置容量, 或者容量设置为0, 说明Channel没有缓存, 只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking). 如果设置了缓存, 就有可能不发生阻塞, 只有buffer满了后send才会阻塞,而只有缓存空了后receive才会阻塞。一个nil channel不会通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
ch1 := make(chan int, 100)
ch2 := make(chan int)

ch1 <- 1 // valid, the data will be stored in the buffer

// ch2 <- 2 // invalid, the main would be blocked forever
// https://stackoverflow.com/questions/36505012/go-fatal-error-all-goroutines-are-asleep-deadlock
go func(){
ch2 <- 2 // if the channel doesn't have buffer, must use goroutine to write and read data
}()

time.Sleep(5000)
}
1
2
3
4
5
6
7
8
9
ch <- v  // send v into Channel ch
v := <- v // receive data from Channel ch

chan T // 双向管道
chan<- float64 // 只用来 发送 float64类型的数据
<-chan int // 只用来 接收 int类型的数据

// blocking mechanism
x, y := <-c, <-c // 这句话会一直等到数据发送到c中才执行

usage of ‘select’

Golang中的select与C++的switch相似,但select只负责监听 IO操作. select经常用来选择一组可能的sender和receiver操作去处理. select中的case语句可以是send也可以是receive,或者default.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func fibonacci(c, quit chan int){
x, y := 0, 1
for{ // recursively input number into c
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) // queue (at most) 10 jobs to fetch from c
}
quit<-0
}()
fibonacci(c, quit); // output 0 1 1 2 3 5 8 13 21 34 quit
}

timeout

select很重要的一个应用就是timeout处理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
ch := make(chan string)
go func(){
time.sleep(time.Second * 2)
ch <- "i'm the data"
}()

select{
case res := <-ch:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timed out!")
}
}

注意time.After方法,它返回一个类型为<-chan Time的单向的channel,在指定的时间发送一个当前时间给返回的channel中.

除此之外,还可以用time.Newtimer()创建一个新的定时器.不过暂时没发现跟time.sleep()的区别 = =

time.NewTicker()创建一个定时触发的定时器,每隔一定时间就会向指定channel发送当前时间.ticker和timer的构造器都会返回相应的通道.

close

向关闭的channel中发送数据会导致panic: send on closed channel.但从关闭的channel中取数据会不断的取到0值.可以用ok确认channel是否已经关闭.

1
2
3
4
5
ch1 := make(chan int)
close(ch1)

i, ok := <-ch1
fmt.Printf("%d, %t", i, ok) //0, false

goroutine(携程)

Golang中go关键字用来创建goroutine, 它在多核cpu环境下是并行的.通常我们使用go关键字和channel实现非阻塞调用. 这里说明了阻塞/非阻塞,同步/异步的区别.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func UnblockedGet(c chan string, url string){
go func(){
request := httplib.Get(url)
content, err := request.String()
if err != nil {
content = "" + err.Error()
}
c <- content
}
}

func main() {
ch1 := make(chan string)
ch2 := make(chan string)
UnblockedGet(ch1, "http://127.0.0.1/test.php?i=1")
UnblockedGet(ch2, "http://127.0.0.1/test.php?i=2")

fmt.Println(<-ch1)
fmt.Println(<-ch2)
}