1. 什么是Goroutines
Goroutines是Golang中一种轻量级的线程方式,一个Goroutine可以看做一个由Go语言调度的独立执行的函数或方法。
1.1 Goroutines的特点
Goroutines比线程更加轻量级,可以开启上万个Goroutines,而线程数量往往只能保持在几千个以下。
Goroutines使用同步原语channel进行通信,使得多个Goroutines之间的共享数据与状态控制更加规范和易于使用,使得程序易于编写和维护。
Goroutines之间的切换比线程更加高效,由于Goroutines共享OS线程,切换的代价比线程的代价要小。
2. Goroutines如何实现高并发
高并发是指系统能够同时处理大量的请求,并且在请求量增大时,系统的吞吐量和响应速度都不会下降。在Golang中,可以使用Goroutines和channel将程序拆散成多个并发执行部分,提高程序的并发性和性能。
2.1 Goroutines的创建和调用
Golang使用关键字“go”来启动一个Goroutine。
func main() {
go func() {
fmt.Println("Hello, Goroutine!")
}()
fmt.Println("Hello, Main!")
}
上面代码中,使用“go”关键字启动了一个新的Goroutine,打印了“Hello, Goroutine!”,同时,在主Goroutine中打印了“Hello, Main!”。
注意:如果在main函数的结尾处没有让主Goroutine休眠,程序将在打印Hello, Main!后直接结束,而无法让启动的子Goroutine执行完毕。
2.2 使用channel进行通信
在Golang中,channel是一种类型,可以用来在Goroutines之间传递数据。channel可以看做一个同步队列,可以安全地传递数据和控制状态。
使用channel进行通信的方式如下:
发信人代码:
func main() {
c := make(chan int)
go func() {
c <- 1
}()
value := <-c
fmt.Println(value)
}
上面代码中,构建了一个带有类型为int的channel,接着在新的Goroutine中将1传入这个channel。在main函数中等待接收这个值,将其打印出来。
收信人代码:
func main() {
c := make(chan int)
go func() {
value := <-c
fmt.Println(value)
}()
c <- 1
}
上面代码中,在新的Goroutine中等待接收来自channel中的值,打印它出来。在主Goroutine中将1传入这个channel。
2.3 使用sync.WaitGroup阻塞并等待所有Goroutine执行完毕
sync.WaitGroup可以用来等待一组Goroutine的执行完毕,它提供了添加和等待Goroutine的方法,可以确保程序在等待的Goroutines全部执行完毕后再结束。
sync.WaitGroup使用方法如下:
添加等待Goroutine:
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
// Do something...
}()
go func() {
defer wg.Done()
// Do something...
}()
wg.Wait()
上面代码中,先使用Add()方法告诉WaitGroup需要等待的Goroutines数量,再在每个子Goroutine中执行Done()方法告诉WaitGroup该子Goroutine已经执行完毕了。最后在主Goroutine中等待所有的子Goroutine执行完毕,使用Wait()方法进行等待。
PS:要确保每个子Goroutine都执行等待操作,确保他们在执行完毕后向WaitGroup发出信号;如果WaitGroup计数器的值为零或负数,调用Wait()方法将立即返回;如果忘记了调用Add()方法,则调用Wait()方法后程序将会直接退出;如果在一个子Goroutine中调用了Done()方法,但没有在Add()方法中增加计数器的数量,程序也会直接退出。
3. Goroutines在网络编程中的应用
在网络编程中,可以使用Golang的net包进行操作。使用Goroutine可以极大地提高网络服务的并发性能。
3.1 并发TCP服务器的实现
下面是一个简单的TCP服务器的例子,该服务器使用Goroutine来同时处理多个客户端请求:
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
go handleConn(conn) // handle connections concurrently
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().String()+"\n")
if err != nil {
return
}
time.Sleep(1 * time.Second)
}
}
在主函数中循环监听客户端的请求,并启动一个新的Goroutine处理该请求。这个程序可以同时接收多个客户端的请求,并为每个请求启动一个新的Goroutine来处理它。
3.2 并发HTTP服务器的实现
下面是一个简单的HTTP服务器的例子,该服务器使用Goroutine来同时处理多个客户端请求:
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
wg.Add(1)
go func() {
defer wg.Done()
// Do something...
}()
wg.Wait()
}
在上面代码中,HTTP请求会在handler()函数中被处理,使用sync.WaitGroup可以同时处理多个客户端请求,当所有的请求都被处理完毕之后,程序才会退出。
3.3 使用context来实现Goroutine的安全取消
在Golang中,可以使用context.Context来实现Goroutine的安全取消。可以使用WithCancel(parentContext)来创建一个新的context,并且在Goroutine中监听这个context是否被取消。如果context被取消,则Goroutine可以退出,释放资源。
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
// Cancelled
return
default:
// Do something...
}
}
}()
// Cancel after a while
time.AfterFunc(1*time.Second, cancel)
}
上面代码中,首先使用context.Background()创建了一个空的根context,再使用WithCancel()创建了一个新的context,并且在新的Goroutine中以select语句的方式监听这个context是否被取消,在main函数中等待一秒钟后调用cancel()方法取消这个context。
4. 总结
本文详细介绍了Golang中如何利用Goroutines来实现高并发。通过对Goroutines创建、使用channel进行通信、使用sync.WaitGroup阻塞并等待所有Goroutine执行完毕和Goroutines在网络编程中的应用等方面进行了讲解,强调了如何使用context来实现Goroutine的安全取消。在上述应用场景中,Goroutines可以很好地实现高并发。