【问题标题】:golang thread safe map with channel as values for thread safegolang线程安全映射,通道作为线程安全的值
【发布时间】:2015-05-20 02:01:25
【问题描述】:

我使用this 作为并发映射,缓冲通道作为线程安全的映射值(作为队列工作),当测试使用 10 个 goroutines 时,从通道获得的值与发送的不同,有什么建议吗?

package main

import "fmt"
import "github.com/streamrail/concurrent-map"

func main() {
    testmap := cmap.New()
    fmt.Println("SyncMapNew:    ", TestInParallel(&testmap, 10))
}

func TestInParallel(g *cmap.ConcurrentMap, n int) time.Duration {
    start := time.Now()
    var wait sync.WaitGroup

    for i := 0; i < n; i++ {
        wait.Add(1)
        go func() {
            TheTest(g, rand.New(rand.NewSource(int64(i*500))))
            wait.Done()
        }()
    }
    wait.Wait()
    return time.Now().Sub(start)
}

func TheTest(g *cmap.ConcurrentMap, rnd *rand.Rand) time.Duration {
    start := time.Now()
    var key string
    var value time.Time
    for i := 0; i < 10000; i++ {
        key = strconv.Itoa(int(rnd.Int31n(50000)))
        if g.Has(key) == false {
            g.Set(key, make(chan time.Time, 100))
        }
        tchan, _ := g.Get(key)
        castchan := tchan.(chan time.Time)
        value = time.Now()
        castchan <- value
        got := <-castchan
        g.Set(key, castchan)
        if value != got {
            panic(fmt.Sprintf("ERROR: expected %v, got %v", value, got))
        }
    }
    return time.Now().Sub(start)
}

更新我误解了业务逻辑,代码应该是这样的

key = strconv.Itoa(int(rnd.Int31n(500)))
tchan, _ := g.GetSet(key, make(chan time.Time, 100))
castchan := tchan.(chan time.Time)
value = time.Now()
if len(castchan) >= 99 {
   <-castchan//do somthing here
}
castchan <- value
g.Set(key, castchan)

【问题讨论】:

  • 鉴于您正在使用的“concurent-map”包中的缺陷(David Budworth's answer 中提到了一些,还有更多),并且您可以轻松添加 sync.Mutex 自己锁定你真正需要的东西(这通常比单独的地图访问多一点)我认为没有理由使用这个包。

标签: dictionary concurrency go


【解决方案1】:

您使用的是随机键,因此多个 goroutine 可能会获得相同的随机数。

如果两个例程在同一(ish)时间获得相同的数字,那么

这是一个竞争条件:

if g.Has(key) == false {
    g.Set(key, make(chan time.Time, 100))
}

有可能两个 goroutine 在同一时间 h.Has 为 false,然后它们都设置了,所以 goroutine1 设置了 chan A,goroutine2 设置了 chan B,然后最终都使用了 chan B

为了解决这个问题,你需要类似的东西

SetIfAbsent

哪个锁,检查它是否存在,如果不存在,设置它,然后解锁。

您链接的库映射不是超级有用的缓存,因为它不提供原子 SetIfAbsent 类型函数。


如果没有 g.Has / g.Set 比赛,那么如果两个例程恰好获得相同的密钥,因此,相同的通道,你不能保证哪个值在队列中是第一个并且先读哪个。

所以 goroutine1 可能会读取 goroutine2 输入的值,或者相​​反。

当考虑在并发执行的系统中使用共享状态时,您必须假设任何其他操作都可能发生在任何语句/代码行之间。

我经常喜欢把它想象成,你应该假设你的每一行代码一次在每个核心上运行。

因此,在 Has/Set 示例中,它将是:

if g.Has(key) == false { // goroutine1
if g.Has(key) == false { // goroutine2
    g.Set(key, make(chan time.Time, 100)) //goroutine1
    g.Set(key, make(chan time.Time, 100)) //goroutine2 
} //goroutine1
} //goroutine2
tchan, _ := g.Get(key) //goroutine1
tchan, _ := g.Get(key) //goroutine2

看看错误在哪里?第二个例程将其频道放在地图中,但两者都在 tchan 线上检索了相同的频道。

有意义吗?

【讨论】:

  • 嗨,我知道你描述的情况,所以我尝试找到并编写一个线程安全映射,我用这个 gist.github.com/craigmj/5770480 进行测试,但是所有三个结构都很慢
  • 您的意思是一遍又一遍地重复使用相同的键吗?您仅在 500 个键上运行 10k 次迭代(因此它们会发生很多冲突)。您要优化哪些实际用例?您的“设置并立即读取”使某些类型的优化变得不可能(您不能批量写入)。这是您的预期用途吗?您真正希望使用哪种密钥?多少?您愿意为性能花费多少内存(如果内存足够,您可以使用数组而不是映射)?密钥真的是随机的吗(我们可以在那里利用一些好处)?用例很重要。
  • @RobNapier,key和value需要在实际情况下重用,post代码只是为了测试通道作为FIFO队列工作,最后我意识到我错过了理解业务逻辑
猜你喜欢
  • 2019-07-08
  • 2014-10-30
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 2020-08-26
  • 1970-01-01
  • 1970-01-01
  • 2020-04-15
相关资源
最近更新 更多