You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在通道更新时启动新Goroutine?解决死锁问题

解决方案:基于通道或条件变量实现URL新增时启动抓取Goroutine

核心思路

Go中实现“数据更新时触发动作”,推荐用通道(优先)或条件变量来实现同步通知,同时保证并发安全,避免死锁。


方案1:用通道传递新URL(推荐)

通道是Go并发模型的核心,天然支持安全的跨goroutine通信,适合作为新URL的输入源,每次发送URL到通道,即可触发抓取goroutine。

package main

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

// 模拟网页抓取函数
func simulateScraping(url string, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("开始抓取: %s\n", url)
	// 模拟抓取耗时
	time.Sleep(1 * time.Second)
	fmt.Printf("完成抓取: %s\n", url)
}

func main() {
	// 用于传递新URL的无缓冲通道
	newLinkChan := make(chan string)
	var wg sync.WaitGroup

	// 启动监听通道的goroutine,收到URL就启动抓取
	go func() {
		for url := range newLinkChan {
			wg.Add(1)
			go simulateScraping(url, &wg)
		}
	}()

	// 模拟新增URL的场景(可替换为实际的URL监控逻辑)
	newLinkChan <- "https://example.com/page1"
	newLinkChan <- "https://example.com/page2"
	newLinkChan <- "https://example.com/page3"

	// 关闭通道:告诉监听goroutine没有新URL需要处理
	close(newLinkChan)

	// 等待所有抓取任务完成,避免主goroutine提前退出
	wg.Wait()
	fmt.Println("所有抓取任务完成")
}

关键说明

  • 通道newLinkChan作为新URL的唯一入口,保证并发安全,无需额外锁。
  • for url := range newLinkChan会持续接收URL,直到通道关闭,避免goroutine泄漏。
  • sync.WaitGroup用于等待所有抓取goroutine执行完毕,解决死锁问题(主goroutine不会提前退出,也不会一直阻塞无意义的等待)。

方案2:用条件变量监听切片变化(适配已有切片场景)

如果必须用切片(如startList)存储URL,可通过sync.Cond实现“切片追加元素时触发通知”,同时用互斥锁保证切片的并发安全。

package main

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

func simulateScraping(url string, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("开始抓取: %s\n", url)
	time.Sleep(1 * time.Second)
	fmt.Printf("完成抓取: %s\n", url)
}

func main() {
	var startList []string
	var mu sync.Mutex
	// 基于互斥锁创建条件变量
	cond := sync.NewCond(&mu)
	var wg sync.WaitGroup

	// 启动监听切片变化的goroutine
	go func() {
		for {
			mu.Lock()
			// 等待切片有新元素(循环避免虚假唤醒)
			for len(startList) == 0 {
				cond.Wait()
			}
			// 取出最后一个新增的URL(根据需求调整处理逻辑)
			url := startList[len(startList)-1]
			startList = startList[:len(startList)-1]
			mu.Unlock()

			// 启动抓取goroutine
			wg.Add(1)
			go simulateScraping(url, &wg)
		}
	}()

	// 模拟向startList追加URL
	mu.Lock()
	startList = append(startList, "https://example.com/page1")
	cond.Signal() // 通知等待的goroutine有新元素
	mu.Unlock()

	time.Sleep(500 * time.Millisecond)

	mu.Lock()
	startList = append(startList, "https://example.com/page2", "https://example.com/page3")
	cond.Broadcast() // 多个元素时用Broadcast唤醒所有等待的goroutine
	mu.Unlock()

	// 等待所有抓取任务完成
	wg.Wait()
	fmt.Println("所有抓取任务完成")
}

关键说明

  • sync.Mutex保护startList的读写,避免并发修改导致的切片损坏。
  • cond.Wait()必须在锁锁定状态下调用,它会自动释放锁并等待,被唤醒后重新获取锁,解决死锁问题。
  • 用Signal()(单个元素)或Broadcast()(多个元素)唤醒等待的goroutine,触发抓取动作。

原代码死锁的常见原因

  1. 通道未关闭:如果用通道接收URL但未关闭,监听goroutine会一直阻塞在接收操作,主goroutine等待所有任务完成时就会陷入死锁。
  2. 无等待机制:主goroutine提前退出,导致抓取goroutine被强制终止,或者主goroutine一直等待但没有goroutine完成任务。
  3. 并发资源竞争:直接修改未加锁的切片,导致数据混乱,goroutine逻辑异常阻塞。

内容的提问来源于stack exchange,提问作者Fergus Johnson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 13:27:19