goroutine
可以简单类比作一个线程
main goroutine
当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。
创建新goroutine
新的goroutine会用go语句来创建。
f() // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait
并发的Clock服务
去掉go只能一个客户端接收到时间,加上go多个客户端可以同时接收到时间
package main
import (
"io"
"log"
"net"
"time"
)
func main() {
// 创建listener,阻塞监听网络端口的连接
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
// 无限循环来接受连接并handle
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
continue
}
// go创建goroutine,让每一次handleConn的调用都进入独立的goroutine
go handleConn(conn)
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return
}
// 每隔一秒调用一次
time.Sleep(1 * time.Second)
}
}
Channels
一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有特殊的类型,也就是channels可发送的数据的类型。比如发送int类型数据的channel一般写为chan int
创建
ch := make(chan int) // 无缓存的channel
ch := make(chan int, 3) // 缓存容量为3的channel
发送和接收
一个channel有发送和接受两个主要操作,都是通信行为。发送和接收两个操作都使用<-运算符
ch <- x // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch // a receive statement; result is discarded
无缓存的Channels(同步Channels)
同步Channels
一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。所以也被称为同步Channels
例子
让主goroutine等待后台goroutine完成工作后再退出。主goroutine中的mustCopy函数返回后,调用conn.Close()关闭连接。主goroutine在退出前先等待从done对应的channel接收一个值。因此总是可以在程序退出前正确输出done消息。
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) // NOTE: ignoring errors
log.Println("done")
done <- struct{}{} // signal the main goroutine
}()
mustCopy(conn, os.Stdin)
conn.Close()
<-done // wait for background goroutine to finish
}
func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
串联的Channels(Pipeline)
Channels也可以用于将多个goroutine连接在一起,一个Channel的输出作为下一个Channel的输入。这种串联的Channels就是所谓的管道(pipeline)。

例子
第一个goroutine是一个计数器,生成1-99,然后通过channel将该整数序列发送给第二个goroutine;第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine;第三个goroutine是一个打印程序,打印收到的每个整数。
func main() {
naturals := make(chan int)
squares := make(chan int)
// Counter
go func() {
for x := 0; x < 100; x++ {
naturals <- x
}
// channels可以不用close,会被垃圾自动回收。但是打开的文件需要手动调用close
close(naturals)
}()
// Squarer
go func() {
// 可以用range循环来依次从channel接收数据,当channel被关闭并且没有值可接收时跳出循环。
for x := range naturals {
squares <- x * x
}
close(squares)
}()
// Printer (in main goroutine)
for x := range squares {
fmt.Println(x)
}
}
单方向的Channel
可以将channel作为函数参数,一般专门用于只发送或者只接收。类型chan<- int表示一个只发送int的channel,类型<-chan int表示一个只接收int的channel,只能接收不能发送。
// 只发送
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}
// 传入一个只发送与一个只接收的channel
func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
close(out)
}
// 只接收
func printer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
printer(squares)
}
带缓存的Channels
带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。
// 创建了一个可以持有三个字符串元素的带缓存Channel
ch = make(chan string, 3)
作用
●初始情况

●发送三个值后
我们可以在无阻塞的情况下连续向新创建的channel发送三个值:
ch <- "A"
ch <- "B"
ch <- "C"
此刻,channel的内部缓存队列将是满的(图8.3),如果有第四个发送操作将发生阻塞。

●接收一个值后
如果我们接收一个值,
fmt.Println(<-ch) // "A"
那么channel的缓存队列将不是满的也不是空的(图8.4),因此对该channel执行的发送或接收操作都不会发生阻塞。通过这种方式,channel的缓存队列解耦了接收和发送的goroutine。

获取缓存容量
// 用内置的cap函数获取
fmt.Println(cap(ch)) // "3"
本文深入探讨Go语言中的并发机制,重点讲解goroutine的使用,包括主goroutine的启动与新goroutine的创建。通过并发的Clock服务示例展示goroutine如何实现多客户端并发服务。接着,详细阐述了channel的概念,包括无缓存与有缓存channel的发送与接收,以及如何利用channel构建并发安全的通信。最后,通过实例展示了如何构建串联的channel形成pipeline,并讨论了单向channel和带缓存channel的应用。

451

被折叠的 条评论
为什么被折叠?



