如何在通道更新时启动新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,触发抓取动作。
原代码死锁的常见原因
- 通道未关闭:如果用通道接收URL但未关闭,监听goroutine会一直阻塞在接收操作,主goroutine等待所有任务完成时就会陷入死锁。
- 无等待机制:主goroutine提前退出,导致抓取goroutine被强制终止,或者主goroutine一直等待但没有goroutine完成任务。
- 并发资源竞争:直接修改未加锁的切片,导致数据混乱,goroutine逻辑异常阻塞。
内容的提问来源于stack exchange,提问作者Fergus Johnson
相关产品推荐
相关产品推荐

