我有一个成千上万个ID的通道,这些通道需要在goroutine中并行处理。我该如何实现锁定,以使goroutine不能同时处理相同的id,是否应该在通道中重复它?
package main
import (
"fmt"
"sync"
"strconv"
"time"
)
var wg sync.WaitGroup
func main() {
var data []string
for d := 0; d < 30; d++ {
data = append(data, "id1")
data = append(data, "id2")
data = append(data, "id3")
}
chanData := createChan(data)
for i := 0; i < 10; i++ {
wg.Add(1)
process(chanData, i)
}
wg.Wait()
}
func createChan(data []string) <-chan string {
var out = make(chan string)
go func() {
for _, val := range data {
out <- val
}
close(out)
}()
return out
}
func process(ids <-chan string, i int) {
go func() {
defer wg.Done()
for id := range ids {
fmt.Println(id + " (goroutine " + strconv.Itoa(i) + ")")
time.Sleep(1 * time.Second)
}
}()
}
-编辑: 所有值都需要以任何顺序进行处理,但是“ id1”,“ id2”和“ id3”需要阻塞,因此它们不能同时由多个goroutine处理。
答案 0 :(得分:1)
这里最简单的解决方案是根本不发送重复的值,然后不需要同步。
func createChan(data []string) <-chan string {
seen := make(map[string]bool)
var out = make(chan string)
go func() {
for _, val := range data {
if seen[val] {
continue
}
seen[val] = true
out <- val
}
close(out)
}()
return out
}
答案 1 :(得分:0)
我找到了解决方案。有人编写了一个程序包(github.com/EagleChen/mapmutex)来完全满足我的需要:
package main
import (
"fmt"
"github.com/EagleChen/mapmutex"
"strconv"
"sync"
"time"
)
var wg sync.WaitGroup
var mutex *mapmutex.Mutex
func main() {
mutex = mapmutex.NewMapMutex()
var data []string
for d := 0; d < 30; d++ {
data = append(data, "id1")
data = append(data, "id2")
data = append(data, "id3")
}
chanData := createChan(data)
for i := 0; i < 10; i++ {
wg.Add(1)
process(chanData, i)
}
wg.Wait()
}
func createChan(data []string) <-chan string {
var out = make(chan string)
go func() {
for _, val := range data {
out <- val
}
close(out)
}()
return out
}
func process(ids <-chan string, i int) {
go func() {
defer wg.Done()
for id := range ids {
if mutex.TryLock(id) {
fmt.Println(id + " (goroutine " + strconv.Itoa(i) + ")")
time.Sleep(1 * time.Second)
mutex.Unlock(id)
}
}
}()
}
答案 2 :(得分:0)
根据定义,您遇到的问题很难解决,我的第一选择是重新架构应用程序以避免出现这种情况,但这不是一个选择:
首先,我假设如果重复给定的ID,您仍然希望对其进行两次处理,但不能并行处理(如果不是这种情况,并且必须忽略第二个实例,那么它将变得更加困难,因为您必须记住每个您已永久处理过的ID,因此您无需对任务执行两次。
要实现您的目标,您必须跟踪goroutine中正在作用的每个ID-go
映射是此处的最佳选择(请注意,其大小会不断增加并行旋转时,最多可以有许多goroutine!)。映射本身必须由锁保护,因为它已被多个goroutine修改。
我要进行的另一种简化是,如果发现从通道中删除的ID被发现正在由另一个gorotuine处理,则可以将其添加回该ID。然后,我们需要map[string]bool
作为跟踪设备,再加上一个sync.Mutex
来保护它。为了简单起见,我假设map,mutex和channel是全局变量。但这可能对您不方便-安排您认为合适的访问(goroutine,闭包等参数)。
import "sync"
var idmap map[string]bool
var mtx sync.Mutex
var queue chan string
func process_one_id(id string) {
busy := false
mtx.Lock()
if idmap[id] {
busy = true
} else {
idmap[id] = true
}
mtx.Unlock()
if busy { // put the ID back on the queue and exit
queue <- id
return
}
// ensure the 'busy' mark is cleared at the end:
defer func() { mtx.Lock(); delete(idmap, id); mtx.Unlock() }()
// do your processing here
// ....
}