Go语言协程Goroutine高级用法(一)
什么协程
在Go语言中,协程就是一种轻量的线程,是并发编程的单元,由Go来管理,所以在GO层面的协程会更加的轻量、高效、开销更小,并且更容易实现并发编程。
轻量级线程
- Go语言中协程(线程)与传统系统层面的线程相比,实在是太轻量了,能小到2kb
- 由于协程的轻量特性,可以更高效地利用系统资源。相较于传统的线程,协程的创建和销毁的开销更小,使得程序更具有扩展性和性能优势。
Go自身管理
- 在 Go 中,这些工作由运行时系统自动完成。这样我们就可以更专注于业务逻辑,而不必过多关心底层线程管理的细节。
并发的基本单元
- 协程是并发编程的基本单元,可以同时执行多个协程,而它们之间的调度和切换由运行时系统负责。
- 在程序中更容易实现高效的并发,处理多个任务而无需显式地创建和管理线程。
- 使用协程,我们可以轻松地实现并发任务,例如同时处理多个网络请求、执行后台任务等。由于协程的轻量特性,可以创建数千甚至数百万个协程而不会造成系统负担。
使用通道通信
- 协程之间可以通过通道进行通信,这是一种在协程之间安全地传递数据和同步操作的机制。通道是一种强大的工具,用于协程之间的协作和数据传递。
协程的基本操作
创建协程
- 语法: `go 函数(函数列表)`
package main
import (
"fmt"
"time")
func Hello() {
fmt.Println("hello world")
}
func main() {
go Hello()
fmt.Println("hello main")
time.Sleep(10 * time.Second)
}
协程与主线程是并发执行的。
协程间通行
- 主要通过
channel
来实现的
package main
import (
"fmt"
)
func sendMessage(ch chan string, msg string) {
ch <- msg
}
func main() {
messagechan := make(chan string)
go sendMessage(messagechan, "Hello World")
msg := <-messagechan
fmt.Println(msg)
}
协程间的同步
- 使用sync包来实现的
- waitgroup 是用来计数信号量的
package main
import (
"fmt"
"sync")
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("worker %d starting\n", id)
fmt.Printf("worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Printf("all workers done\n")
}
waitgroup确保主线程等待所有协程完成
协程的错误处理
- 使用
select
语句和通道可以实现协程的错误处理
package main
import (
"fmt"
"time")
func dosomething(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "hello world"
}
func main() {
ch := make(chan string)
go dosomething(ch)
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}
}
select
语句允许在多个通道操作中选择一个可用的操作,可以用来处理协程的超时等情况。
协程的高级操作
协程池
- 协程池是一组预先创建的协程,用于执行并发任务,可以避免频繁创建和销毁协程的开销。
- 使用缓冲通道来实现协程池
package main
import (
"fmt"
"sync")
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
results <- j * 2
}
}
func main() {
const numJobs = 5
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
worker(i, jobs, results)
}(i)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
go func() {
wg.Wait()
close(results)
}()
for result := range results {
fmt.Println("result", result)
}
}
三个协程形成了协程池,从任务通道
jobs
中获取任务,处理后将结果发送到结果通道results
超时控制
package main
import (
"fmt"
"time")
func dosomething(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "hello world"
}
func main() {
ch := make(chan string)
go dosomething(ch)
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}
}
time.After
创建一个计时器,如果在指定时间内没有从通道ch
中接收到结果,就会触发超时。
协程的取消
- 使用
context
包提供的上下文(Context)来实现协程的取消。
package main
import (
"context"
"fmt" "time")
func doSomething(ctx context.Context, ch chan string) {
select {
case <-ctx.Done():
ch <- "task completed successfully"
case <-time.After(1 * time.Second):
ch <- "task timed out"
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch := make(chan string)
go doSomething(ctx, ch)
time.Sleep(2 * time.Second)
cancel()
result := <-ch
fmt.Println(result)
}
通过调用
cancel
函数取消协程的执行。