Go从语言本身支持并发,而不是由某个库或者模块来实现并发,可谓天生丽质。goroutine从根本上与线程不同,goroutine更加轻量化。
看下面这个常见的网络模型:
package main
import (
"fmt"
"net"
)
func manageClient(conn net.Conn) {
conn.Write([]byte("Hi!"))
conn.Close()
//do something with the client
}
func main() {
//we are creating a server her that listenson port 1337.
listener, err := net.Listen("tcp", ":1337")
for {
//accept a connection
connection, _ := listener.Accept()
go manageClient(connection)
}
}
在main函数调用net.Listen方法进行监听,该方法会返回两个值,一个是服务器连接,另一个是错误数据。然后,进入到服务的主循环部分,程序调用server.Accept方法,然后等待请求。该方法调用后,程序会被挂起,直到有有一个客户端的连接出现。一旦有个连接出现,我们将connection对象传值到manageClient方法中,由于通过goroutine的方式调用manageClient,所以主程序会继续等待处理下一个客户端连接请求。
上面的代码清晰明了,Go允许使用go语句开启一个新的运行期线程,即 goroutine,以一个不同的、新创建的goroutine来执行一个函数。同一个程序中的所有goroutine共享同一个地址空间。
Goroutine非常轻量,除了为之分配的栈空间,其所占用的内存空间微乎其微。并且其栈空间在开始时非常小,之后随着堆存储空间的按需分配或释放而变化。内部实现上,goroutine会在多个操作系统线程上多路复用。如果一个goroutine阻塞了一个操作系统线程,例如:等待输入,这个线程上的其他goroutine就会迁移到其他线程,这样能继续运行。
让我们接着来看下面这个例子:
package main
import (
"fmt"
)
func sayHello() {
fmt.Println("Hello, world!")
}
func main() {
//run a goroutine that says hello
go sayHello()
}
上述程序会输出什么?什么也不会输出,因为sayHello这个goroutine还没来得及跑,主函数已经退出了。
在C++/Java/Python里面,都有类似Join的东东来等待子线程,而go里面是用Channels来实现的,Channels是一种goroutine之间或者goroutine和主进程之间的通信机制。
把上述程序改为:
package main