Go并发编程中的经验教训("Go并发编程实战:经验与教训总结")

原创
ithorizon 6个月前 (10-21) 阅读数 14 #后端开发

Go并发编程实战:经验与教训总结

一、并发编程概述

Go语言是一种赞成并发编程的语言,其并发模型基于Goroutine、Channel和Mutex等机制。并发编程可以节约程序的性能,但同时也带来了许多挑战。本文将总结Go并发编程的一些实战经验和教训。

二、Goroutine的使用经验

1. 合理控制Goroutine的数量

过多的Goroutine会占用大量内存,甚至引起系统崩溃。应选用实际场景合理控制Goroutine的数量。可以使用Goroutine池来局限并发数,如下所示:

package main

import (

"fmt"

"sync"

)

func main() {

var wg sync.WaitGroup

poolSize := 10

sem := make(chan struct{}, poolSize)

for i := 0; i < 100; i++ {

wg.Add(1)

sem <- struct{}{}

go func(i int) {

defer wg.Done()

fmt.Println("处理任务", i)

<-sem

}(i)

}

wg.Wait()

}

2. 不要在Goroutine中直接调用os.Exit()或panic()

这样做会引起整个程序退出或异常,应避免在Goroutine中直接调用os.Exit()或panic()。如果需要终止程序,可以采用以下对策:

package main

import (

"fmt"

"os"

"os/signal"

"syscall"

)

func main() {

sigs := make(chan os.Signal, 1)

signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

go func() {

<-sigs

fmt.Println("收到信号,退出程序")

os.Exit(0)

}()

// 业务逻辑

}

三、Channel的使用经验

1. 选择合适的Channel类型

Go语言提供了多种类型的Channel,如无缓冲Channel、有缓冲Channel和单向Channel。应选用实际需求选择合适的Channel类型。例如,如果生产者和消费者速度不匹配,可以使用有缓冲Channel:

package main

import (

"fmt"

"sync"

)

func main() {

var wg sync.WaitGroup

ch := make(chan int, 10) // 有缓冲Channel

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

ch <- i

}

close(ch)

}()

wg.Add(1)

go func() {

defer wg.Done()

for v := range ch {

fmt.Println(v)

}

}()

wg.Wait()

}

2. 不要在Channel中发送nil

在Go中,向Channel发送nil是合法的,但这样会引起接收者接收到一个nil,而不是期望的数据。应避免在Channel中发送nil。

四、Mutex的使用经验

1. 尽量避免使用Mutex

Mutex是一种互斥锁,用于保护共享资源。但使用Mutex会增长程序的纷乱度,降低性能。在也许的情况下,应避免使用Mutex。例如,可以使用Channel来传递共享资源,而不是使用Mutex。

2. 不要在Goroutine中持有Mutex

在Goroutine中持有Mutex会引起死锁,考虑到Goroutine也许会在持有Mutex的情况下被阻塞。如果需要在线程间共享资源,可以使用Cond或其他同步机制。

五、其他经验与教训

1. 使用Context来传递取消信号

Context是Go语言中用于传递取消信号的机制。通过使用Context,可以方便地取消子Goroutine的执行,从而避免资源浪费。以下是一个示例:

package main

import (

"context"

"fmt"

"sync"

"time"

)

func main() {

ctx, cancel := context.WithCancel(context.Background())

var wg sync.WaitGroup

wg.Add(1)

go func() {

defer wg.Done()

for {

select {

case <-ctx.Done():

fmt.Println("子Goroutine被取消")

return

default:

fmt.Println("子Goroutine执行中...")

time.Sleep(1 * time.Second)

}

}

}()

time.Sleep(3 * time.Second)

cancel()

wg.Wait()

}

2. 使用WaitGroup来等待Goroutine完成

WaitGroup是Go语言中用于等待一组Goroutine完成的同步机制。通过使用WaitGroup,可以确保所有Goroutine执行完成后再进行后续操作。以下是一个示例:

package main

import (

"fmt"

"sync"

)

func main() {

var wg sync.WaitGroup

wg.Add(2)

go func() {

defer wg.Done()

fmt.Println("Goroutine 1执行完成")

}()

go func() {

defer wg.Done()

fmt.Println("Goroutine 2执行完成")

}()

wg.Wait()

fmt.Println("所有Goroutine执行完成")

}

3. 避免使用共享变量

在并发编程中,共享变量是引起问题的常见原因。应尽量使用Channel或其他同步机制来传递数据,而不是直接操作共享变量。

4. 使用日志记录并发操作

日志是调试并发程序的重要工具。通过记录关键的操作和状态,可以方便地定位问题。可以使用标准库的log包来记录日志:

package main

import (

"fmt"

"log"

"os"

)

func main() {

logFile, err := os.OpenFile("goroutine.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)

if err != nil {

log.Fatalf("打开日志文件落败:%v", err)

}

defer logFile.Close()

log.SetOutput(logFile)

log.Println("Goroutine起初执行...")

// 业务逻辑

log.Println("Goroutine执行完成")

}

六、总结

Go并发编程是一种强劲的编程范式,可以节约程序的性能。但并发编程也带来了许多挑战,需要开发者掌握一定的技巧和经验。本文总结了Go并发编程的一些实战经验和教训,期望对读者有所帮助。


本文由IT视界版权所有,禁止未经同意的情况下转发

文章标签: 后端开发


热门