项目经常遇到一些批量任务执行太慢,需要开启多线程去处理,记录下在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
来协调和同步不同的协程。