首页 > 程序源码 > Golang异步编程方式和技巧
2024
06-01

Golang异步编程方式和技巧

Golang基于多线程、协程实现,与生俱来适合异步编程,当我们遇到那种需要批量处理且耗时的操作时,传统的线性执行就显得吃力,这时就会想到异步并行处理。下面介绍一些异步编程方式和技巧。

一、使用方式

1.1、最简单的最常用的方式:使用go关键词

func main { go func { fmt.Println("hello world1") } go func { fmt.Println("hello world2") } }

或者:

func main { go Announce("hello world1") go Announce("hello world2") } func Announce(message string) { fmt.Println(message) }

使用匿名函数传递参数

data := "Hello, World!" go func(msg string) { // 使用msg进行异步任务逻辑处理 fmt.Println(msg) }(data)

这种方式不需要考虑返回值问题,如果要考虑返回值,可以使用下面的方式。

1.2、通过goroutine和channel来实现

ch := make(chan int, 1) // 创建一个带缓冲的channel // ch := make(chan int, 0) // 创建一个无缓冲的channel go func { // 异步任务逻辑 ch <- result // 将结果发送到channel // 异步任务逻辑 close(ch) // 关闭channel,表示任务完成 } // 在需要的时候从channel接收结果 result := <-ch

1.3、使用sync.WaitGroup

sync.WaitGroup用于等待一组协程完成其任务。通过Add方法增加等待的协程数量,Done方法标记协程完成,Wait方法阻塞直到所有协程完成。

var wg sync.WaitGroup // 启动多个协程 for i := 0; i < 5; i++ { wg.Add(1) go func(index int) { defer wg.Done // 异步任务逻辑 }(i) } // 等待所有协程完成 wg.Wait

1.4、使用errgroup实现协程组的错误处理

如果想简单获取协程返回的错误,errgroup包很适合,errgroup包是Go语言标准库中的一个实用工具,用于管理一组协程并处理它们的错误。可以使用errgroup.Group结构来跟踪和处理协程组的错误。

var eg errgroup.Group for i := 0; i < 5; i++ { eg.Go(func error { return errors.New("error") }) eg.Go(func error { return nil }) } if err := eg.Wait; err != nil { // 处理错误 } 二、一些使用技巧

2.1、使用channel的range和close操作

range操作可以在接收通道上迭代值,直到通道关闭。可以使用close函数关闭通道,以向接收方指示没有更多的值。

ch := make(chan int) go func { for i := 0; i < 5; i++ { ch <- i // 发送值到通道 } close(ch) // 关闭通道 } // 使用range迭代接收通道的值 for val := range ch { // 处理接收到的值 }

2.2、使用select语句实现多个异步操作的等待

ch1 := make(chan int) ch2 := make(chan string) go func { // 异步任务1逻辑 ch1 <- result1 } go func { // 异步任务2逻辑 ch2 <- result2 } // 在主goroutine中等待多个异步任务完成 select { case res1 := <-ch1: // 处理结果1 case res2 := <-ch2: // 处理结果2 }

2.3、使用select和time.After实现超时控制

如果需要在异步操作中设置超时,可以使用select语句结合time.After函数实现。

ch := make(chan int) go func { // 异步任务逻辑 time.Sleep(2 * time.Second) ch <- result } // 设置超时时间 select { case res := <-ch: // 处理结果 case <-time.After(3 * time.Second): // 超时处理 }

2.4、使用select和time.After实现超时控制

如果需要在异步操作中设置超时,可以使用select语句结合time.After函数实现。

ch := make(chan int) go func { // 异步任务逻辑 time.Sleep(2 * time.Second) ch <- result } // 设置超时时间 select { case res := <-ch: // 处理结果 case <-time.After(3 * time.Second): // 超时处理 }

2.5、使用time.Tick和time.After进行定时操作

time.Tick函数返回一个通道,定期发送时间值,可以用于执行定时操作。time.After函数返回一个通道,在指定的时间后发送一个时间值。

tick := time.Tick(1 * time.Second) // 每秒执行一次操作 for { select { case <-tick: // 执行定时操作 } } select { case <-time.After(5 * time.Second): // 在5秒后执行操作 }

2.6、使用sync.Mutex或sync.RWMutex进行并发安全访问

当多个协程并发访问共享数据时,需要确保数据访问的安全性。sync.Mutex和sync.RWMutex提供了互斥锁和读写锁,用于在访问共享资源之前进行锁定,以避免数据竞争。sync.RWMutex是一种读写锁,可以在多个协程之间提供对共享资源的并发访问控制。多个协程可以同时获取读锁,但只有一个协程可以获取写锁。

var mutex sync.Mutex var data int // 写操作,使用互斥锁保护数据 mutex.Lock data = 123 mutex.Unlock // 读操作,使用读锁保护数据 //RLock加读锁时,如果存在写锁,则无法加读锁;当只有读锁或者没有锁时,可以加读锁,读锁可以加载多个 mutex.RLock value := data mutex.RUnlock var rwMutex sync.RWMutex var sharedData map[string]string // 读操作,使用rwMutex.RLock读锁保护数据 func readData(key string) string { rwMutex.RLock defer rwMutex.RUnlock return sharedData[key] } // 写操作,使用rwMutex.Lock写锁保护数据 func writeData(key, value string) { rwMutex.Lock defer rwMutex.Unlock sharedData[key] = value }

注意:sync.Mutex 的锁是不可以嵌套使用的 sync.RWMutex 的 RLock是可以嵌套使用的 sync.RWMutex 的 mu.Lock 是不可以嵌套的 sync.RWMutex 的 mu.Lock 中不可以嵌套 mu.RLock

2.7、使用sync.Cond进行条件变量控制

sync.Cond是一个条件变量,用于在协程之间进行通信和同步。它可以在指定的条件满足之前阻塞等待,并在条件满足时唤醒等待的协程。

var cond = sync.NewCond(&sync.Mutex{}) var ready bool go func { // 异步任务逻辑 ready = true // 通知等待的协程条件已满足 cond.Broadcast } // 在某个地方等待条件满足 cond.L.Lock for !ready { cond.Wait } cond.L.Unlock

2.8、使用sync.Pool管理对象池

sync.Pool是一个对象池,用于缓存和复用临时对象,可以提高对象的分配和回收效率。

type MyObject struct { // 对象结构 } var objectPool = sync.Pool{ New: func interface{} { // 创建新对象 return &MyObject{} }, } // 从对象池获取对象 obj := objectPool.Get.(*MyObject) // 使用对象 // 将对象放回对象池 objectPool.Put(obj)

2.9、使用sync.Once实现只执行一次的操作

sync.Once用于确保某个操作只执行一次,无论有多少个协程尝试执行它,常用于初始化或加载资源等场景。

var once sync.Once var resource *Resource func getResource *Resource { once.Do(func { // 执行初始化资源的操作,仅执行一次 resource = initResource }) return resource } // 在多个协程中获取资源 go func { res := getResource // 使用资源 } go func { res := getResource // 使用资源 }

2.10、使用sync.Once和context.Context实现资源清理

可以结合使用sync.Once和context.Context来确保在多个协程之间只执行一次资源清理操作,并在取消或超时时进行清理。

var once sync.Once func cleanup { // 执行资源清理操作 } func doTask(ctx context.Context) { go func { select { case <-ctx.Done: once.Do(cleanup) // 只执行一次资源清理 } } // 异步任务逻辑 }

2.11、使用sync.Map实现并发安全的映射

sync.Map是Go语言标准库中提供的并发安全的映射类型,可在多个协程之间安全地进行读写操作。

var m sync.Map // 存储键值对 m.Store("key", "value") // 获取值 if val, ok := m.Load("key"); ok { // 使用值 } // 删除键 m.Delete("key")

2.12、使用context.Context进行协程管理和取消

context.Context用于在协程之间传递上下文信息,并可用于取消或超时控制。可以使用context.WithCancel创建一个可取消的上下文,并使用context.WithTimeout创建一个带有超时的上下文。

ctx, cancel := context.WithCancel(context.Background) go func { // 异步任务逻辑 if someCondition { cancel // 取消任务 } } // 等待任务完成或取消 select { case <-ctx.Done: // 任务被取消或超时 }

2.13、使用context.WithDeadline和context.WithTimeout设置截止时间

context.WithDeadline和context.WithTimeout函数可以用于创建带有截止时间的上下文,以限制异步任务的执行时间。

func doTask(ctx context.Context) { // 异步任务逻辑 select { case <-time.After(5 * time.Second): // 超时处理 case <-ctx.Done: // 上下文取消处理 } } func main { ctx := context.Background ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel go doTask(ctx) // 继续其他操作 }

2.14、使用context.WithValue传递上下文值

context.WithValue函数可用于在上下文中传递键值对,以在协程之间共享和传递上下文相关的值。

type keyContextValue string func doTask(ctx context.Context) { if val := ctx.Value(keyContextValue("key")); val != nil { // 使用上下文值 } } func main { ctx := context.WithValue(context.Background, keyContextValue("key"), "value") go doTask(ctx) // 继续其他操作 }

2.15、使用atomic包进行原子操作

atomic包提供了一组函数,用于实现原子操作,以确保在并发环境中对共享变量的读写操作是原子的。

var counter int64 func increment { atomic.AddInt64(&counter, 1) } func main { var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func { defer wg.Done increment } } wg.Wait fmt.Println("Counter:", counter) }


最后编辑:
作者:萌小白
一个热爱网络的青年!

发布评论

表情