Golang中如何优雅地运行两个协程并让第二个有条件执行

Golang中如何优雅地运行两个协程并让第二个有条件执行 我想并发执行两个操作,每个操作都会设置一个变量(或返回一个结果)。

第二个 goroutine 将有条件地执行。

如果第二个 goroutine没有执行,则第二个 goroutine 设置(或返回)的结果等于第一个的结果。

我想知道处理这种情况的最优和最地道的方式是什么。

1. 字面函数调用

var (
   v1, v2 int
   wg sync.WaitGroup
)

wg.Add(2)
go func(){
 v1 = anotherfunc(param1)
  wg.Done()
}
if something{
  go func(){
    v2 = anotherfunc(param2)
    wg.Done()
  }
} else {
  v2 = v1
  wg.Done()
}
wg.Wait()

2. 使用通道进行正常函数调用

go anotherfunc(param1, channel1)
if something {
  go anotherfunc(param2, channel2)
  var2 <- channel2
}
var1 <- channel1
if !something {
 var2 = var1
}

编辑:我现在意识到第一种方法有一个缺陷。我们无法保证第一个 goroutine 会在 v2 = v1 赋值之前返回。不确定是否可以通过一些补充来保留第一种方法。


更多关于Golang中如何优雅地运行两个协程并让第二个有条件执行的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

如果想保留第一种方法,你可以先等待第一个任务完成,然后根据条件决定是否添加第二个任务:

func main() {
	var (
		v1, v2 int
		wg     sync.WaitGroup
	)
	wg.Add(1)
	go func() {
		v1 = longRunning()
		wg.Done()
	}()
	wg.Wait()
	fmt.Println("First:", v1)
	if v1 < 50 {
		// 无需额外操作
		v2 = v1
	} else {
		wg.Add(1)
		go func() {
			v2 = longRunning()
			wg.Done()
		}()
		wg.Wait()
	}
	fmt.Println("Second:", v2)
}

请注意,为了模拟延迟,我使用了一个休眠后返回伪随机数的函数:

// 模拟一个长时间运行的任务
func longRunning() int {
	fmt.Println("Starting long-running job...")
	time.Sleep(500 * time.Millisecond)
	randSource := rand.NewSource(time.Now().UnixNano())
	randGen := rand.New(randSource)
	return randGen.Intn(100)
}

无论如何,我认为这种通用方法相当易读/不错。不过,我在想是否可以使用通道,并始终发送两个值,这样主函数就不关心它如何获取这些值,只要它能获取到就行。由于你是按顺序运行任务的,你只需要一个 goroutine:

func main() {
	var v1, v2 int
	results := make(chan int)
	go func() {
		result := longRunning()
		results <- result
		// 我们需要进行一些额外的处理来确定第二个值
		if result > 50 {
			result = longRunning()
		}
		// 发送第二个值
		results <- result
	}()
	v1 = <-results
	fmt.Println("First:", v1)
	v2 = <-results
	fmt.Println("Second:", v2)
}

这在我看来相当简洁。当然,你需要非常小心错误处理,并确保通道总是发送两个值。

更多关于Golang中如何优雅地运行两个协程并让第二个有条件执行的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理这种条件性并发执行,最优雅的方式是使用sync.Once配合通道。这样可以确保第二个协程要么执行计算,要么复用第一个结果,且保证线程安全。

package main

import (
	"fmt"
	"sync"
	"time"
)

func compute(id int, delay time.Duration) int {
	time.Sleep(delay)
	return id * 10
}

func main() {
	var (
		result1, result2 int
		once             sync.Once
		wg               sync.WaitGroup
		ch               = make(chan int, 1)
		something        = false // 条件变量
	)

	wg.Add(1)
	go func() {
		defer wg.Done()
		result1 = compute(1, 100*time.Millisecond)
		ch <- result1

		// 如果第二个协程未执行,确保result2获得结果
		once.Do(func() {
			result2 = result1
		})
	}()

	if something {
		wg.Add(1)
		go func() {
			defer wg.Done()
			once.Do(func() {
				result2 = compute(2, 50*time.Millisecond)
			})
		}()
	}

	wg.Wait()
	close(ch)

	// 验证结果
	fmt.Printf("Result1: %d\n", result1)
	fmt.Printf("Result2: %d\n", result2)
	fmt.Printf("Results equal: %v\n", result1 == result2)
}

更简洁的通道方案:

func main() {
	something := false
	ch1 := make(chan int, 1)
	ch2 := make(chan int, 1)

	go func() { ch1 <- compute(1, 100*time.Millisecond) }()

	if something {
		go func() { ch2 <- compute(2, 50*time.Millisecond) }()
	} else {
		go func() { ch2 <- <-ch1 }() // 复用ch1的结果
	}

	result1 := <-ch1
	result2 := <-ch2

	fmt.Printf("Result1: %d, Result2: %d\n", result1, result2)
}

使用select实现超时控制:

func main() {
	something := false
	ch1 := make(chan int, 1)
	ch2 := make(chan int, 1)
	timeout := time.After(200 * time.Millisecond)

	go func() { ch1 <- compute(1, 100*time.Millisecond) }()

	if something {
		go func() { ch2 <- compute(2, 150*time.Millisecond) }()
	}

	select {
	case result1 := <-ch1:
		result2 := result1
		if something {
			select {
			case result2 = <-ch2:
			case <-timeout:
				result2 = result1 // 超时则使用第一个结果
			}
		}
		fmt.Printf("Results: %d, %d\n", result1, result2)
	case <-timeout:
		fmt.Println("Timeout!")
	}
}

这些方案保证了:

  1. 条件性执行第二个协程
  2. 第二个结果要么来自独立计算,要么复用第一个结果
  3. 线程安全且无数据竞争
  4. 代码清晰易读
回到顶部