Go语言之并发编程(三)
前言
在前两篇文章中,我们已经学习了Go语言中并发编程的基础知识,包括协程的创建、通道的使用、锁的机制等。本文将继续深入讲解一些更加高级和实用的并发编程技巧,希望对你有所帮助。
Go语言的并行处理
在很多情况下,我们需要处理大量数据或者进行一些复杂的计算,这时候就需要用到并行处理来提高程序的执行效率。Go语言提供了一些很好的方式来进行并行处理。
goroutine池
在Go语言中,如果我们需要启动大量的goroutine,则可能会导致内存泄漏或者系统资源不足。为了避免这种情况的发生,我们可以考虑使用goroutine池来管理并发的数量。goroutine池可以控制goroutine的数量,避免系统资源的过度使用。
下面是一个使用goroutine池的示例代码:
package main
import (
"fmt"
"sync"
)
type Task struct {
ID int
}
type Worker struct {
ID int
TaskChan chan Task
}
func (w Worker) start(wg *sync.WaitGroup) {
defer wg.Done()
for t := range w.TaskChan {
fmt.Printf("Worker %d got task %d\n", w.ID, t.ID)
// process task
}
}
func main() {
jobQueue := make(chan Task, 100)
var workers []Worker
for i := 0; i < 10; i++ {
worker := Worker{
ID: i,
TaskChan: make(chan Task),
}
workers = append(workers, worker)
go worker.start(&wg)
}
for i := 0; i < 100; i++ {
job := Task{ID: i}
jobQueue <- job
}
close(jobQueue)
wg.Wait()
}
在这个例子中,我们创建了10个goroutine,然后使用Task通道来任务分配。我们需要处理100个任务,每个任务对应一个Task结构体。当我们把所有的任务分配完成后,使用close函数关闭通道,然后等待所有的goroutine执行完成。这样,我们就成功地使用goroutine池控制了并发的数量。
并行计算与并发计算
在Go语言中,我们可以使用并行和并发两种方式来实现计算任务的加速。
并行计算是指将一个大任务划分为多个互不干扰的小任务,每个小任务由一个独立的线程运行,多个小任务可以同时运行,从而实现整个计算任务的加速。这种方式可以适用于一些密集型的计算任务。
并发计算是指在一个线程中,通过使用协程等方式,将一个任务分解成若干个子任务,子任务之间可以并行执行。这种方式通常适用于一些I/O密集型的任务。
下面是一个并行计算的示例代码:
package main
import (
"fmt"
"math/rand"
"time"
)
func calculate(id int, limit int, resultChan chan int, doneChan chan bool) {
sum := 0
for i := 0; i < limit; i++ {
sum += rand.Intn(100)
}
resultChan <- sum
doneChan <- true
}
func main() {
rand.Seed(time.Now().UnixNano())
taskNum := 100
taskLimit := 1000000
resultChan := make(chan int, taskNum)
doneChan := make(chan bool, taskNum)
startTime := time.Now()
for i := 0; i < taskNum; i++ {
go calculate(i, taskLimit, resultChan, doneChan)
}
total := 0
for i := 0; i < taskNum; i++ {
<-doneChan
total += <-resultChan
}
endTime := time.Now()
fmt.Printf("Total sum is %d, cost %f seconds", total, endTime.Sub(startTime).Seconds())
}
在这个例子中,我们创建了100个goroutine,并使用resultChan通道来获取每个goroutine的计算结果。使用doneChan通道来通知主线程每个任务的完成情况。然后在主线程中,我们使用for循环来等待所有的任务完成,然后累加所有的计算结果。
sync包的使用
在Go语言中,sync包提供了一些很好的同步和条件机制。
WaitGroup
在许多并发程序中,我们需要等待多个goroutine执行完成后再继续执行后续操作。这个时候,我们可以使用sync包中的WaitGroup实现等待所有goroutine结束后再继续执行。
下面是一个WaitGroup的示例代码:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d started\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
在这个例子中,我们创建了5个goroutine,并使用WaitGroup控制这些goroutine的运行。使用Add方法增加需要等待执行的goroutine数量,使用Done方法来表示goroutine执行完成,使用Wait方法等待所有goroutine执行完成。
Mutex
在并发程序中,对于临界区资源的读写操作需要同步,否则就会出现竞态条件等问题。在Go语言中,我们可以使用Mutex来保护临界区资源,避免出现并发问题。
下面是一个Mutex的示例代码:
package main
import (
"fmt"
"sync"
)
var (
mutex sync.Mutex
balance int
)
func deposit(amount int) {
mutex.Lock()
balance += amount
mutex.Unlock()
}
func withdraw(amount int) {
mutex.Lock()
balance -= amount
mutex.Unlock()
}
func main() {
deposit(100)
withdraw(50)
fmt.Printf("Balance is %d", balance)
}
在这个例子中,我们使用了Mutex来保护balance这个资源。deposit和withdraw方法在对balance进行操作时需要先加锁,执行完成后再解锁,这样就保证了多个goroutine对balance的操作不会相互干扰。
结语
通过本文的学习,相信读者已经对Go语言中的并行和并发编程有了更深入的了解,并掌握了一些实用的技巧和方法。在进行并发编程的时候,需要注意不同的场景选择不同的技术和方式,避免出现问题和影响程序性能。同时也要注意代码的可读性和可维护性,这样才能保证程序的质量和稳定性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Go语言之并发编程(三) - Python技术站