1. 什么是Goroutines?
Goroutines是Go语言中的一种并发机制,它是一种轻量级的线程,由Go语言的运行时(runtime)调度。Goroutines可以在有些地方替代回调函数,使得代码更加简单、易读,同时还能极大地提高程序的并发性。
1.1 Goroutines的创建
创建Goroutines非常简单,只需要在函数前面加上"go"关键字即可:
func main() {
go foo()
go bar()
}
这里启动了两个Goroutines,一个执行foo()函数,另一个执行bar()函数。注意,这两个Goroutines是并发执行的,而不是串行执行的。
1.2 Goroutines和线程的区别
Goroutines和线程的区别在于,Goroutines是轻量级的线程,可以更好地利用计算机的多核处理器,而且创建和销毁Goroutines的代价非常小,可以轻松创建百万个Goroutines。相比之下,线程的创建和销毁代价就比较大了。
此外,Goroutines采用的是协作式调度,因此它们的切换代价也非常小。相比之下,线程采用的是抢占式调度,每次线程切换都需要保存和恢复线程的上下文,切换代价比Goroutines高得多。
2. Goroutines的优雅用法
2.1 利用Goroutines并发下载
下面是一个使用Goroutine并发下载多个文件的例子:
func downloadFile(url string) {
// 发起HTTP请求,下载文件
}
func main() {
urls := []string{"http://example.com/file1.txt", "http://example.com/file2.txt", "http://example.com/file3.txt"}
for _, url := range urls {
go downloadFile(url)
}
// 等待所有Goroutines结束
var wg sync.WaitGroup
wg.Add(len(urls))
wg.Wait()
}
这个程序会下载urls数组中指定的多个文件,并发执行多个下载任务,从而提高下载速度。
2.2 利用Goroutines处理并发请求
下面是一个使用Goroutine处理并发请求的例子:
func handleRequest(conn net.Conn) {
// 处理请求
}
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatal("Error: ", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Error: ", err)
}
go handleRequest(conn)
}
}
这个程序会监听8000端口的TCP连接,每当有一个客户端连接上来时,就会发起一个Goroutine来处理这个连接。
2.3 利用Goroutines处理任务队列
下面是一个使用Goroutine处理任务队列的例子:
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
// 处理任务
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// 创建若干个Goroutines处理任务
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 往任务队列中添加任务
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// 输出任务结果
for a := 1; a <= 9; a++ {
<-results
}
}
这个程序会创建3个Goroutines来处理任务队列中的任务,每个任务都会被处理一次,并将结果存入results通道中。
3. Goroutines的调度
3.1 Goroutines的调度策略
Go语言的运行时(runtime)会自动调度Goroutines,根据当前Goroutines的执行情况和系统负载情况,自动调整Goroutines的调度策略,以提高程序的并发性和性能。
Go语言的调度器采用的是M:N调度模型,即将M个Goroutines分配到N个系统线程上执行。M和N都是可以配置的,默认情况下M等于CPU核数,N等于10000。
3.2 Goroutines的阻塞和唤醒
Goroutines在遇到I/O操作或者调用time.Sleep()等函数时,会被阻塞,进入等待状态,此时Go语言的运行时会自动将该Goroutine从系统线程上解绑,放入等待队列中,等待I/O完成或time.Sleep()时间结束后被唤醒。
当等待的I/O操作完成或者time.Sleep()时间到了后,Go语言的运行时会自动唤醒被阻塞的Goroutine,将其重新加入到系统线程中执行。
3.3 Goroutines的调度强制切换
Go语言的运行时有时候会强制进行Goroutines的切换,以避免某个Goroutine长时间占用系统线程,从而导致其他Goroutines无法执行的情况。
如果某个Goroutine的执行时间超过10ms,就会被运行时强制切换,并将该Goroutine挂起,等待下次调度。
4. 总结
Goroutines是Go语言中非常强大、灵活的并发机制,能够轻松处理大量的并发任务,提高程序的并发性和性能。
通过本文的介绍,我们学习了Goroutines的基本概念和用法,以及Goroutines的调度策略和机制,能够更好地理解和使用Goroutines,写出更加优雅的并发程序。