golang 学习 - chan
1. 通道
// _通道_ 是连接多个 Go 协程的管道。你可以从一个 Go 协程
// 将值发送到通道,然后在别的 Go 协程中接收。
package main
import "fmt"
func main() {
// 使用 `make(chan val-type)` 创建一个新的通道。
// 通道类型就是他们需要传递值的类型。
messages := make(chan string)
// 使用 `channel <-` 语法 _发送_ 一个新的值到通道中。这里
// 我们在一个新的 Go 协程中发送 `"ping"` 到上面创建的
// `messages` 通道中。
go func() { messages <- "ping" }()
// 使用 `<-channel` 语法从通道中 _接收_ 一个值。这里
// 将接收我们在上面发送的 `"ping"` 消息并打印出来。
msg := <-messages
fmt.Println(msg)
}
2. 通道缓存
// 默认通道是 _无缓冲_ 的,这意味着只有在对应的接收(`<- chan`)
// 通道准备好接收时,才允许进行发送(`chan <-`)。_可缓存通道_
// 允许在没有对应接收方的情况下,缓存限定数量的值。
package main
import "fmt"
func main() {
// 这里我们 `make` 了一个通道,最多允许缓存 2 个值。
messages := make(chan string, 2)
// 因为这个通道是有缓冲区的,即使没有一个对应的并发接收
// 方,我们仍然可以发送这些值。
messages <- "buffered"
messages <- "channel"
// 然后我们可以像前面一样接收这两个值。
fmt.Println(<-messages)
fmt.Println(<-messages)
}
3. 通道同步
// 我们可以使用通道来同步 Go 协程间的执行状态。这里是一个
// 使用阻塞的接受方式来等待一个 Go 协程的运行结束。
package main
import "fmt"
import "time"
// 这是一个我们将要在 Go 协程中运行的函数。`done` 通道
// 将被用于通知其他 Go 协程这个函数已经工作完毕。
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
// 发送一个值来通知我们已经完工啦。
done <- true
}
func main() {
// 运行一个 worker Go协程,并给予用于通知的通道。
done := make(chan bool, 1)
go worker(done)
// 程序将在接收到通道中 worker 发出的通知前一直阻塞。
<-done
}
4. 通道方向
// 当使用通道作为函数的参数时,你可以指定这个通道是不是
// 只用来发送或者接收值。这个特性提升了程序的类型安全性。
package main
import "fmt"
// `ping` 函数定义了一个只允许发送数据的通道。尝试使用这个通
// 道来接收数据将会得到一个编译时错误。
func ping(pings chan<- string, msg string) {
pings <- msg
}
// `pong` 函数允许通道(`pings`)来接收数据,另一通道
// (`pongs`)来发送数据。
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
5. 通道选择器
package main
import "time"
import "fmt"
func main() {
// 在我们的例子中,我们将从两个通道中选择。
c1 := make(chan string)
c2 := make(chan string)
// 各个通道将在若干时间后接收一个值,这个用来模拟例如
// 并行的 Go 协程中阻塞的 RPC 操作
go func() {
time.Sleep(time.Second * 1)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
// 我们使用 `select` 关键字来同时等待这两个值,并打
// 印各自接收到的值。
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
6. 超时处理
package main
import "time"
import "fmt"
func main() {
// 在我们的例子中,假如我们执行一个外部调用,并在 2 秒后
// 通过通道 `c1` 返回它的执行结果。
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
// 这里是使用 `select` 实现一个超时操作。
// `res := <- c1` 等待结果,`<-Time.After` 等待超时
// 时间 1 秒后发送的值。由于 `select` 默认处理第一个
// 已准备好的接收操作,如果这个操作超过了允许的 1 秒
// 的话,将会执行超时 case。
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
7. 定时器
package main
import "time"
import "fmt"
func main() {
// 定时器表示在未来某一时刻的独立事件。你告诉定时器
// 需要等待的时间,然后它将提供一个用于通知的通道。
// 这里的定时器将等待 2 秒。
timer1 := time.NewTimer(time.Second * 2)
// `<-timer1.C` 直到这个定时器的通道 `C` 明确的发送了
// 定时器失效的值之前,将一直阻塞。
<-timer1.C
fmt.Println("Timer 1 expired")
// 如果你需要的仅仅是单纯的等待,你需要使用 `time.Sleep`。
// 定时器是有用原因之一就是你可以在定时器失效之前,取消这个
// 定时器。这是一个例子
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
}
8. 打点器
package main
import "time"
import "fmt"
func main() {
// 打点器和定时器的机制有点相似:一个通道用来发送数据。
// 这里我们在这个通道上使用内置的 `range` 来迭代值每隔
// 500ms 发送一次的值。
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
// 打点器可以和定时器一样被停止。一旦一个打点停止了,
// 将不能再从它的通道中接收到值。我们将在运行后 1500ms
// 停止这个打点器。
time.Sleep(time.Millisecond * 1500)
ticker.Stop()
fmt.Println("Ticker stopped")
}
9. 工作池
package main
import "fmt"
import "time"
// 这是我们将要在多个并发实例中支持的任务了。这些执行者
// 将从 `jobs` 通道接收任务,并且通过 `results` 发送对应
// 的结果。我们将让每个任务间隔 1s 来模仿一个耗时的任务。
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "processing job", j)
time.Sleep(time.Second)
results <- j * 2
}
}
func main() {
// 为了使用 worker 工作池并且收集他们的结果,我们需要
// 2 个通道。
jobs := make(chan int, 100)
results := make(chan int, 100)
// 这里启动了 3 个 worker,初始是阻塞的,因为
// 还没有传递任务。
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 这里我们发送 9 个 `jobs`,然后 `close` 这些通道
// 来表示这些就是所有的任务了。
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// 最后,我们收集所有这些任务的返回值。
for a := 1; a <= 9; a++ {
<-results
}
}