前言
项目经常遇到一些批量任务执行太慢,需要开启多线程去处理,记录下在Golang
中协程使用的一些操作。
协程介绍
协程是计算机程序的一类组件,推广了协作式多任务的子例程,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程更适合于用来实现彼此熟悉的程序组件,如协作式多任务、异常处理、事件循环、迭代器、无限列表和管道。
协程与线程的区别在于:
- 协程的创建和切换开销更小,因此可以创建更多的协程。
- 协程之间是异步的,也就是说,一个协程的执行不会阻塞另一个协程的执行。
- 协程的调度是由系统来进行的,程序员不需要手动进行调度。
协程的优势在于:
- 可以实现高效的并发编程。
- 可以简化并发编程的代码。
- 可以提高并发编程的性能。
协程的常见用例包括:
- 异步网络操作。
- 异步文件操作。
- 异步数据库操作。
- 异步计算。
- 事件循环。
Golang 语言还提供了 channel
数据结构来用于协程之间的通信。
channel
介绍
在Go语言中,通道(Channel)是一种用于在协程之间进行通信和同步的数据结构。通道提供了一种安全的方式,使一个协程可以向另一个协程发送数据,并保证在接收方准备好之前,发送方会被阻塞。通道是Go语言并发编程的核心机制之一。
以下是关于Golang中通道的一些基本介绍:
-
通道的创建:
使用make
函数创建一个通道,通道的类型是数据传递的类型。通道可以是带缓冲的(Buffered Channel)或无缓冲的(Unbuffered Channel)。// 无缓冲通道 ch := make(chan int) // 带缓冲通道,容量为3 chBuffered := make(chan int, 3)
-
通道的发送和接收:
使用<-
运算符进行通道的发送和接收操作。发送和接收操作都是原子的,这有助于避免竞态条件。// 发送数据到通道 ch <- 42 // 从通道接收数据 value := <-ch
-
无缓冲通道:
无缓冲通道在发送和接收数据时,发送方和接收方必须同时准备好,否则它们将阻塞等待。ch := make(chan int) go func() { // 发送数据到通道 ch <- 42 }() // 从通道接收数据 value := <-ch
-
带缓冲通道:
带缓冲通道允许在通道满之前缓存一定数量的元素,发送和接收操作变得非阻塞,直到通道满或为空。ch := make(chan int, 3) // 发送数据到通道,不会阻塞 ch <- 42 ch <- 43 ch <- 44
-
关闭通道:
使用close
函数关闭通道,关闭后的通道不能再发送数据,但可以继续接收已发送的数据。close(ch)
-
通道的选择(
select
语句):
select
语句用于在多个通道操作中进行选择,以处理多路通信。select { case msg1 := <-ch1: fmt.Println("Received", msg1) case msg2 := <-ch2: fmt.Println("Received", msg2) default: fmt.Println("No communication") }
-
通道的方向:
可以通过在通道类型中指定方向,限制通道的发送或接收操作。// 只能发送数据到通道 func sendData(ch chan<- int) { ch <- 42 } // 只能从通道接收数据 func receiveData(ch <-chan int) { value := <-ch }
通道是Go语言中非常强大和灵活的并发原语,能够帮助你构建安全、高效的并发程序。通过良好的通道使用,可以避免共享数据的竞态条件,提高程序的可维护性和稳定性。
基础使用
在Go语言中,协程(Goroutine)是一种轻量级的线程,由Go语言的运行时系统调度。协程使得并发编程更加容易和高效。下面是一些关于Golang协程基础使用的示例:
-
创建协程:
使用关键字go
可以创建一个新的协程。以下是一个简单的例子:package main import ( "fmt" "time" ) func main() { go sayHello() time.Sleep(1 * time.Second) // 等待协程执行 } func sayHello() { fmt.Println("Hello, Goroutine!") }
这个例子中,
sayHello
函数被放在一个新的协程中运行。 -
传递参数给协程:
如果你需要将参数传递给协程,可以使用闭包。以下是一个例子:package main import ( "fmt" "time" ) func main() { msg := "Hello, Goroutine!" go func(m string) { fmt.Println(m) }(msg) time.Sleep(1 * time.Second) }
在这个例子中,我们创建了一个匿名函数,并在协程启动时传递了
msg
参数。 -
等待协程执行完成:
如果主程序需要等待协程执行完成,可以使用sync
包或者channel
来实现。以下是一个使用sync
包的例子:package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() fmt.Println("Hello, Goroutine!") }() wg.Wait() }
在这个例子中,
sync.WaitGroup
用于等待协程执行完成。 -
使用 Channel 进行通信:
协程之间通过 Channel 进行通信。以下是一个简单的例子:package main import ( "fmt" ) func main() { messages := make(chan string) go func() { messages <- "Hello, Goroutine!" }() msg := <-messages fmt.Println(msg) }
这个例子中,
messages
是一个字符串类型的 channel,协程通过<-
运算符发送消息,主程序通过<-
运算符接收消息。
这些是一些基础的 Golang 协程使用的例子。在实际开发中,协程的使用可以更加复杂,涉及到协程之间的同步、错误处理等方面的考虑。
协程中传参数示例
下面是一个示例,其中一个协程负责执行任务,接收参数,返回结果,而主程序等待协程完成并获取结果:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
resultCh := make(chan int, 1)
wg.Add(1)
go worker(5, &wg, resultCh)
wg.Wait()
// 从通道中获取结果
result := <-resultCh
fmt.Println("Result:", result)
}
func worker(value int, wg *sync.WaitGroup, resultCh chan int) {
defer wg.Done()
// 模拟一些工作
result := value * 2
// 将结果发送到通道
resultCh <- result
}
在这个示例中,worker
函数是一个协程,负责执行任务。它接收一个整数参数 value
,执行一些工作(这里简单地将参数乘以2),然后将结果发送到结果通道 resultCh
。
主程序创建了一个 sync.WaitGroup
用于等待协程完成,还创建了一个通道 resultCh
用于接收结果。主程序启动了一个协程并传递了参数5给 worker
函数。然后,主程序使用 Wait
方法等待协程完成。
在协程执行完成后,它会将结果发送到通道,并主程序通过 <-resultCh
语句从通道中获取结果。最后,主程序打印出获取的结果。
这个例子演示了如何在协程中执行任务,传递参数,获取结果,并等待协程完成。
协程中channel
和select
使用
下面是一个带有管道和 select
的复杂示例。在这个示例中,有两个协程,一个生成随机数并将其发送到管道,另一个协程从管道接收随机数并执行不同的任务。主程序使用 select
语句等待这两个协程的完成:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
randCh := make(chan int)
resultCh := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go randomNumberGenerator(randCh, &wg)
go processNumbers(randCh, resultCh, &wg)
go func() {
wg.Wait()
close(resultCh)
}()
// 使用 select 语句等待协程完成
for {
select {
case num, ok := <-resultCh:
if !ok {
fmt.Println("All tasks completed.")
return
}
fmt.Println("Processed number:", num)
case <-time.After(2 * time.Second):
fmt.Println("Timeout: No result received in 2 seconds.")
return
}
}
}
func randomNumberGenerator(randCh chan int, wg *sync.WaitGroup) {
defer wg.Done()
rand.Seed(time.Now().UnixNano())
for i := 0; i < 5; i++ {
randomNum := rand.Intn(100)
fmt.Println("Generated random number:", randomNum)
randCh <- randomNum
time.Sleep(500 * time.Millisecond)
}
close(randCh)
}
func processNumbers(randCh chan int, resultCh chan int, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case num, ok := <-randCh:
if !ok {
fmt.Println("Number generator closed.")
return
}
// 模拟处理任务
result := num * 2
resultCh <- result
}
}
}
在这个示例中,randomNumberGenerator
协程生成随机数并将其发送到 randCh
管道。processNumbers
协程从 randCh
管道接收随机数,并执行一些任务,将结果发送到 resultCh
管道。
主程序使用 select
语句等待 resultCh
管道中的结果,同时设置一个 2 秒的超时。如果在超时之前没有接收到结果,主程序会输出超时消息。这个例子演示了如何使用管道和 select
来协调和同步不同的协程。