Go语言的协程(Goroutine)是一种轻量级的线程,可以在Go语言中高效地并发执行。
它们由Go语言运行时环境管理,并且不需要像传统线程那样显式地创建和销毁。相反,你只需要使用关键字 go
就可以启动一个新的协程运行一个函数。
下面是一个简单的例子:
func main() {
go hello()
time.Sleep(1 * time.Second)
}
func hello() {
fmt.Println("Hello, world!")
}
在这个例子中,我们使用关键字 go
启动了一个新的协程来运行 hello()
函数。main()
函数会等待 1 秒钟,以确保协程有足够的时间完成它的工作。
当你运行这段代码时,你会看到输出 "Hello, world!"。这证明了协程已经在后台运行,并且不会阻塞主线程的执行。
Go语言的协程是非常轻量级的,因此可以在同一进程中创建数百万个协程而不会造成任何问题。这使得Go语言成为编写高并发应用程序的理想选择。
Go 创建协程非常的简单,但是在实际项目中,其实我们需要的是怎么通过并发协程去合并多个协程的处理结果。
以下是正在学习go的过程中使用的几种动态创建协程并合并协程结果的方式。
1.使用channel
首先理解什么是缓存型 channel 和非缓存型 channel
无缓存型 channel: 无缓存型 channel 又称为同步 channel,它是指在接收数据前没有能力保存任何值的 channel。这种类型的 channel 要求发送者和接收者同时准备好,才能完成数据传输。如果发送者没有接收者来取走数据,那么它就会一直阻塞在发送操作上。同样地,如果接收者没有发送者发送数据,那么它也会一直阻塞在接收操作上。这种 channel 的主要作用是保证 Goroutine 的同步执行。
下面是一个无缓存型 channel 的示例代码:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
fmt.Println("Start")
ch <- 1
fmt.Println("End")
}()
fmt.Println(<-ch)
}
在这个例子中,我们使用 make 函数初始化了一个无缓存型 channel。协程会先输出 Start,然后通过 channel 发送了一个值 1,最后输出 End。在 main Goroutine 中,我们通过 <-ch
的方式从 channel 中接收一个值,并将其打印出来。由于这是一个无缓存型 channel,所以协程必须等待 main Goroutine 接收数据后,才能继续往下执行。
缓存型 channel: 缓存型 channel 是指在发送数据时能够保存一定数量的值的 channel。它允许发送者在没有接收者的情况下发送多个值,只有当 channel 内部的缓存都被填满后,发送者才会阻塞。同样地,只有在缓存为空时,接收者才会阻塞等待数据。这种 channel 的主要作用是增加 channel 的吞吐量。
下面是一个缓存型 channel 的示例代码:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
在这个例子中,我们使用 make 函数初始化了一个可以保存 3 个整数的缓存型 channel。然后我们连续向 channel 中发送了三个整数,然后依次从 channel 中接收这些整数并将其打印出来。由于 channel 是带缓存的,所以我们可以先发送所有的数据,然后再依次读取数据,这大大提高了程序的效率。
以下是利用channel实现合并动态创建的协程结果的代码:
package main
import (
"fmt"
"time"
)
func GenerateData(ch chan []string, t string) {
time.Sleep(5 * time.Second)
Datas := make([]string, 0, 5)
if t == "City" {
Datas = append(Datas, "上海", "北京", "广州", "重庆", "安庆")
}
if t == "Brand" {
Datas = append(Datas, "本田", "丰田", "大众", "江淮", "奇瑞")
}
if t == "Family" {
Datas = append(Datas, "CRV", "URV", "荣放", "瑞风", "QQ")
}
if t == "Price" {
Datas = append(Datas, "100000", "20000", "30000", "400000", "500000")
}
ch <- Datas
}
func main() {
startTime := time.Now()
channels := make([]chan []string, 4)
tags := []string{"City", "Brand", "Family", "Price"}
for i, v := range tags {
channels[i] = make(chan []string)
go GenerateData(channels[i], v)
}
datas := make([][]string, 0, 4)
for _, ch := range channels {
data := <-ch
datas = append(datas, data)
close(ch)
}
fmt.Printf("datas is -------------->%v\n", datas)
fmt.Printf("程序结束\n")
costTime := time.Since(startTime)
fmt.Printf("程序运行时间: %v", costTime)
}
2.使用互斥锁
Go 语言中的互斥锁是一种常用的控制共享资源访问的方法,用于保护共享资源不被并发访问和修改。它可以确保同一时间只有一个 goroutine(Go 语言中的轻量级线程)可以访问共享资源。
在 Go 语言中,可以使用 sync.Mutex
类型来创建互斥锁。这个类型包含两个方法:Lock()
和 Unlock()
,分别用于获取和释放锁。
例如,下面是一个使用互斥锁保护一个整数变量的示例:
package main
import (
"fmt"
"sync"
)
var counter int
var mutex sync.Mutex
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
incrementCounter()
}()
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
func incrementCounter() {
mutex.Lock()
defer mutex.Unlock()
counter++
}
在上面的示例中,我们首先定义了一个整数变量 counter
和一个互斥锁 mutex
。然后我们启动了 10 个 goroutine,每个 goroutine 都会对 counter
进行累加操作。由于 counter
是共享资源,因此我们需要通过互斥锁来保护它,确保同一时间只有一个 goroutine 可以访问它。每个 goroutine 在执行累加操作之前需要先调用 mutex.Lock()
获取锁,在操作完成之后再调用 mutex.Unlock()
释放锁。最后,我们等待所有 goroutine 执行完成,然后输出 counter
的最终值。
总的来说,使用互斥锁可以有效地避免多个 goroutine 同时访问共享资源而导致的数据竞争问题,从而提高程序的并发性能。
第二点我们还要知道Go 语言中的通道(channel)可以是阻塞性的。
当通过一个非缓冲通道发送数据时,在发送操作完成之前,发送方将被阻塞,直到接收方从该通道中读取数据为止。同样,当通过非缓冲通道接收数据时,在接收操作完成之前,接收方将被阻塞,直到发送方向该通道发送数据为止。
另一方面,通过一个带缓存通道发送数据时,只有在通道已满时才会阻塞发送方。同样,当通过一个带缓存通道接收数据时,只有在通道为空时才会阻塞接收方。
因此,通道可以是阻塞性的,具体取决于通道的类型和使用方式。
所以我们使用互斥锁实现合并动态创建的协程结果还要使用阻塞行为阻塞主协程,避免主协程结束比子协程快导致数据错误。
sync.WaitGroup
是 Go 语言中的一个内置结构体,提供了协程同步机制。它允许您在继续程序的其余部分之前等待一组协程完成执行。
下面是 sync.WaitGroup
的使用示例:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// do some work
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
在这个示例中,我们创建了一个 WaitGroup
变量 wg
。然后我们循环一系列任务,并调用 wg.Add(1)
来增加活动工作者的计数器。我们还将 WaitGroup
的指针传递给每个工作者函数。
在工作函数内部,我们延迟调用 wg.Done()
,当函数退出时减少活动工作者的计数器。
最后,在所有工作者都已启动后,我们调用 wg.Wait()
,它会阻塞直到活动工作者的计数器达到零。一旦所有工作者都已完成其工作,就会打印出“所有工作者都完成了”的消息。
以下是利用互斥锁实现合并动态创建的协程结果的代码:
package main
import (
"fmt"
"sync"
"time"
)
var mutex sync.Mutex
var wg sync.WaitGroup
func main() {
startTime := time.Now()
tags := []string{"City", "Brand", "Family", "Price"}
datas := make([][]string, 0, 4)
for _, v := range tags {
wg.Add(1)
go func(v string) {
tagDatas := make([]string, 0, 5)
if v == "City" {
tagDatas = append(tagDatas, "上海", "北京", "广州", "重庆", "安庆")
}
if v == "Brand" {
tagDatas = append(tagDatas, "本田", "丰田", "大众", "江淮", "奇瑞")
}
if v == "Family" {
tagDatas = append(tagDatas, "CRV", "URV", "荣放", "瑞风", "QQ")
}
if v == "Price" {
tagDatas = append(tagDatas, "100000", "20000", "30000", "400000", "500000")
}
mutex.Lock()
datas = append(datas, tagDatas)
mutex.Unlock()
wg.Done()
}(v)
}
wg.Wait()
var overAllData []string
for _, v := range datas {
overAllData = append(overAllData, v...)
}
fmt.Printf("overAllData is -------------->%v\n", overAllData)
fmt.Printf("程序结束\n")
costTime := time.Since(startTime)
fmt.Printf("程序运行时间: %v", costTime)
}
以上是两种go动态创建协程并合并协程结果的实现方式,后续可能会补充其他的实现方式,因为博主还在没有深入了解go的一些机制,如有不合理的地方请指正