测试后expvar.Map :加锁的map : sync.map = 0.75 : 1 : 3

anko · 2020-03-25 03:23:55 · 2286 次点击 · 预计阅读时间 1 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2020-03-25 03:23:55 的文章,其中的信息可能已经有所发展或是发生改变。

如题,白白浪费我几天时间,偶发的并发bug不少,看我上篇文章就出现了各种bug,就不说了,现在来说个一定会出现的bug,

下面的代码在1.14.1版本是会报错的,结果不为1,但是我卸载后重新安装1.13.9之后就可以正常执行了!当然也可能是我的电脑原因,求大伙验证!

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    var m sync.Map

    for i := 0; i < 65536; i++ {
        m.Store(1, 1)
    }
    wg.Add(2)
    go Add(m)
    go sub(m)

    wg.Wait()
    fmt.Println(m.Load(1))
}
func Add(m sync.Map) {
    for i := 0; i < 10000; i++ {
        value, _ := m.Load(1)
        v := value.(int)
        v += 1
        m.Store(1, v)
    }
    wg.Done()
}

func sub(m sync.Map) {
    for i := 0; i < 10000; i++ {
        value, _ := m.Load(1)
        v := value.(int)
        v -= 1
        m.Store(1, v)
    }
    wg.Done()
}

不出意外的话,上面的额结果不会绝对是1 !!!大伙可以试试!我就不去PR了!

似乎1.13.9也会出现,可能真是我的电脑环境问题了!


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2286 次点击  ∙  1 赞  
加入收藏 微博
上一篇:golang堆排序
下一篇:go map 学习
43 回复  |  直到 2020-03-26 18:01:40
anko
anko · #1 · 5年之前

目测要被打脸?

saberlong
saberlong · #2 · 5年之前

你写的add和sub非线程(协程)安全的。 需要加锁或改成原子操作。

anko
anko · #3 · 5年之前
saberlongsaberlong #2 回复

你写的add和sub非线程(协程)安全的。 需要加锁或改成原子操作。

难道sync.map不就是为了解决并发安全问题?我没懂,求指教!

saberlong
saberlong · #4 · 5年之前

3楼 @anko sync.Map保证的并发安全是指Store和Load操作自身,而不是保证取到的值在其他操作时也是安全的。 Store操作包含了hash、查找、插入等等处理,这些操作无法在一个时钟周期内完成。 <br/>
<br/> 没有做并发安全的情况下,两个协程同时进行Store时, A协程在查找时没有,插入了一个值,B协程在查找时也没有值,但在插入覆盖了A插入的值。 <br/> <br/> 并发安全是保证了Store不会出现上面这种情况。同样Load是保证了不会读到正在Store而没有Store完成的值。 <br/> <br/> 你写的代码就是同样道理。不算v := value.(int)操作之外(不知道golang这个包含列哪些操作)。 Load这个是原子操作,v += 1分别包含了取值、加法计算、赋值三个操作, m.Store(1, v)是原子操作。 所以你的逻辑里起码有5个操作,只有保证这5个操作是原子的,才能算并发安全。

zhengkeyu
zhengkeyu · #5 · 5年之前
saberlongsaberlong #4 回复

3楼 @anko sync.Map保证的并发安全是指Store和Load操作自身,而不是保证取到的值在其他操作时也是安全的。 Store操作包含了hash、查找、插入等等处理,这些操作无法在一个时钟周期内完成。

没有做并发安全的情况下,两个协程同时进行Store时, A协程在查找时没有,插入了一个值,B协程在查找时也没有值,但在插入覆盖了A插入的值。

并发安全是保证了Store不会出现上面这种情况。同样Load是保证了不会读到正在Store而没有Store完成的值。

你写的代码就是同样道理。不算v := value.(int)操作之外(不知道golang这个包含列哪些操作)。 Load这个是原子操作,v += 1分别包含了取值、加法计算、赋值三个操作, m.Store(1, v)是原子操作。 所以你的逻辑里起码有5个操作,只有保证这5个操作是原子的,才能算并发安全。

但是我用go run -race main.go 不会出现资源竞争的报错啊

sunhui200475
sunhui200475 · #6 · 5年之前

其实sync.Map是指你对改map增、删、查是安全的,map操作需要在操作前上锁,保护内存,用sync.Map你不用上锁;这和两个线程同步没有关系,线程还是竞争关系

anko
anko · #7 · 5年之前
saberlongsaberlong #4 回复

3楼 @anko sync.Map保证的并发安全是指Store和Load操作自身,而不是保证取到的值在其他操作时也是安全的。 Store操作包含了hash、查找、插入等等处理,这些操作无法在一个时钟周期内完成。

没有做并发安全的情况下,两个协程同时进行Store时, A协程在查找时没有,插入了一个值,B协程在查找时也没有值,但在插入覆盖了A插入的值。

并发安全是保证了Store不会出现上面这种情况。同样Load是保证了不会读到正在Store而没有Store完成的值。

你写的代码就是同样道理。不算v := value.(int)操作之外(不知道golang这个包含列哪些操作)。 Load这个是原子操作,v += 1分别包含了取值、加法计算、赋值三个操作, m.Store(1, v)是原子操作。 所以你的逻辑里起码有5个操作,只有保证这5个操作是原子的,才能算并发安全。

也就是,你的意思是,这种map并不保证读写互斥?(ー_ー)!!,仅仅是把锁的粒度缩小了而已?那么,我还需要给读写加上锁,那么,我现在的疑问是:对于并发安全需求,我用了这种map似乎更加损耗性能!!!!所以,你能举个它的应用场景吗?感谢

anko
anko · #8 · 5年之前
sunhui200475sunhui200475 #6 回复

其实sync.Map是指你对改map增、删、查是安全的,map操作需要在操作前上锁,保护内存,用sync.Map你不用上锁;这和两个线程同步没有关系,线程还是竞争关系

如果我还是要用到锁,那我感觉就没必要用到这种map了,我试了下,用了反而更加损耗性能!你能举个应用场景吗?最好有代码,谢谢!!

sdghchj
sdghchj · #9 · 5年之前

你说的报错是有panic异常吗?

anko
anko · #10 · 5年之前
sdghchjsdghchj #9 回复

你说的报错是有panic异常吗?

不是,是结果不为1,是我对sync.map理解错误了!

zhengkeyu
zhengkeyu · #11 · 5年之前

我运行了很多次没有panic go version go1.14 windows/amd64

anko
anko · #12 · 5年之前

@zhengkeyu 我不是指报错,我要保证1加减1万次1还是1,不是panic,是线程安全,数据安全,线程同步

anko
anko · #13 · 5年之前

经过测试,如果在sync.map等等操作需要原子化的时候,还在外层加上锁来保证map的各种操作原子化的话,那么,他的速度仅仅是go原生的map(非sync.map)的性能的3分之一,不相信的朋友可以自己测试一个大数据就知道了,所以我很好奇设计sync.map的初衷是什么!!!我想不到他任何的应用场景!

saberlong
saberlong · #14 · 5年之前

5楼 @zhengkeyu 目前根据你的描述,在多个地方理解有问题。 建议你多了解下程序的原理。特别是各个数据类型的在内存中是如何存在的。 cpu是如何访问cpu缓存数据、以及内存数据的。 多核cpu如何保证在多线程下,保证内存中的数据一致的。 你说不会出现data race是因为你使用的是int。你分析下你的代码中哪些是分配在栈上,哪些是在堆中。 int传入sync.Map后是在内存中是如何存储的。当执行完你的逻辑后,变量和值都发生了什么变化。

给你改下就会出现data race了.

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

type Foo struct {
    Value int
}

func main() {
    var m sync.Map

    for i := 0; i < 65536; i++ {
        m.Store(1, &Foo{Value: 1})
    }
    wg.Add(2)
    go Add(m)
    go sub(m)

    wg.Wait()
    fmt.Println(m.Load(1))
}

func Add(m sync.Map) {
    for i := 0; i < 10000; i++ {
        value, _ := m.Load(1)
        f := value.(*Foo)
        f.Value += 1
        m.Store(1, f)
    }
    wg.Done()
}

func sub(m sync.Map) {
    for i := 0; i < 10000; i++ {
        value, _ := m.Load(1)
        f := value.(*Foo)
        f.Value -= 1
        m.Store(1, f)
    }
    wg.Done()
}
saberlong
saberlong · #15 · 5年之前
zhengkeyuzhengkeyu #5 回复

#4楼 @saberlong 但是我用go run -race main.go 不会出现资源竞争的报错啊

同时再给你一个不会出现data race的例子

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var wg sync.WaitGroup

type Foo struct {
    Value int32
}

func main() {
    m := make(map[int]*Foo)

    for i := 0; i < 65536; i++ {
        m[1] = &Foo{Value: 1}
    }
    wg.Add(2)
    go Add(m)
    go sub(m)

    wg.Wait()
    fmt.Println(m[1])
}
func Add(m map[int]*Foo) {
    for i := 0; i < 10000; i++ {
        f := m[1]
        atomic.AddInt32(&f.Value, 1)
    }
    wg.Done()
}

func sub(m map[int]*Foo) {
    for i := 0; i < 10000; i++ {
        f := m[1]
        atomic.AddInt32(&f.Value, -1)
    }
    wg.Done()
}
anko
anko · #16 · 5年之前

经过测试,我发现如下性能比例:expvar.Map :加锁的map : sync.map = 0.75 : 1 : 3,

saberlong
saberlong · #17 · 5年之前
zhengkeyuzhengkeyu #5 回复

#4楼 @saberlong 但是我用go run -race main.go 不会出现资源竞争的报错啊

...第一个回复的前面一段话不是回复给你的。把你当发贴人了

saberlong
saberlong · #18 · 5年之前
ankoanko #12 回复

@zhengkeyu 我不是指报错,我要保证1加减1万次1还是1,不是panic,是线程安全,数据安全,线程同步

看我回复zhengkeyu 的例子,把他当作你了。直接从中文意思浅层面的理解往往带来问题,还是得从程序原理上理解。 至于场景作用需要具体分析。 一个典型的场景是单个生产者,多消费者场景。

anko
anko · #19 · 5年之前
saberlongsaberlong #15 回复

#5楼 @zhengkeyu 同时再给你一个不会出现data race的例子 ```` package main import ( "fmt" "sync" "sync/atomic" ) var wg sync.WaitGroup type Foo struct { Value int32 } func main() { m := make(map[int]*Foo) for i := 0; i < 65536; i++ { m[1] = &Foo{Value: 1} } wg.Add(2) go Add(m) go sub(m) wg.Wait() fmt.Println(m[1]) } func Add(m map[int]*Foo) { for i := 0; i < 10000; i++ { f := m[1] atomic.AddInt32(&f.Value, 1) } wg.Done() } func sub(m map[int]*Foo) { for i := 0; i < 10000; i++ { f := m[1] atomic.AddInt32(&f.Value, -1) } wg.Done() } ````

其实说白了,就是sync.map的锁粒度是局限于锁读和锁写,但是读和写可以同时进行,大概就是这个意思是吧?

anko
anko · #20 · 5年之前

我发现expvar.Map才是真正我需要的结构,就是map的值读写是原子性的!

anko
anko · #21 · 5年之前
saberlongsaberlong #18 回复

#12楼 @anko 看我回复zhengkeyu 的例子,把他当作你了。直接从中文意思浅层面的理解往往带来问题,还是得从程序原理上理解。 至于场景作用需要具体分析。 一个典型的场景是单个生产者,多消费者场景。

非常感谢,不过真心求一个sync.map的应用场景!我新人不大懂!非常感谢2位大佬!敬上!

saberlong
saberlong · #22 · 5年之前

@anko 对于局限于锁读和锁写,不知道和你的是否一致。 感觉你的理解是 Store伪代码:

Lock
Store
Unlock

Load伪代码:

Lock
Load
Unlock

这么理解上,使用层面感觉不会有问题。但是建议还是从原理层面理解,更通用,任何语言都融会贯通

saberlong
saberlong · #23 · 5年之前
ankoanko #20 回复

我发现expvar.Map才是真正我需要的结构,就是map的值读写是原子性的!

expvar.Map其实就是sync.Map的包装, 源码很少。理解我上面给的2个例子后,就知道了。

anko
anko · #24 · 5年之前
saberlongsaberlong #23 回复

#20楼 @anko expvar.Map其实就是sync.Map的包装, 源码很少。理解我上面给的2个例子后,就知道了。

确实是sync.Map的包装,经过层层赛选,我已经将原本的6分钟优化到了1分3秒,最后还是抛弃了各种map,使用了数组和atomic.addint64()来做,我想分段计数应该还能优化到1分钟以下!非常感谢你的帮助!不是说map不好,而是我的业务不大适合!啊哈哈!

zhengkeyu
zhengkeyu · #25 · 5年之前
saberlongsaberlong #14 回复

5楼 @zhengkeyu 目前根据你的描述,在多个地方理解有问题。 建议你多了解下程序的原理。特别是各个数据类型的在内存中是如何存在的。 cpu是如何访问cpu缓存数据、以及内存数据的。 多核cpu如何保证在多线程下,保证内存中的数据一致的。 你说不会出现data race是因为你使用的是int。你分析下你的代码中哪些是分配在栈上,哪些是在堆中。 int传入sync.Map后是在内存中是如何存储的。当执行完你的逻辑后,变量和值都发生了什么变化。 给你改下就会出现data race了. ```` package main import ( "fmt" "sync" ) var wg sync.WaitGroup type Foo struct { Value int } func main() { var m sync.Map for i := 0; i < 65536; i++ { m.Store(1, &Foo{Value: 1}) } wg.Add(2) go Add(m) go sub(m) wg.Wait() fmt.Println(m.Load(1)) } func Add(m sync.Map) { for i := 0; i < 10000; i++ { value, _ := m.Load(1) f := value.(*Foo) f.Value += 1 m.Store(1, f) } wg.Done() } func sub(m sync.Map) { for i := 0; i < 10000; i++ { value, _ := m.Load(1) f := value.(*Foo) f.Value -= 1 m.Store(1, f) } wg.Done() } ````

你这样改肯定就不行了啊 因为存的是指针 2个goroutine同时对一个内存地址进行写 之前他存的是值类型

saberlong
saberlong · #26 · 5年之前
zhengkeyuzhengkeyu #25 回复

#14楼 @saberlong 你这样改肯定就不行了啊 因为存的是指针 2个goroutine同时对一个内存地址进行写 之前他存的是值类型

例子一仅仅是对你说go run -race main.go没有data race而写的例子。用来比较为什么他原来的例子没有data race。

例子二不能算通用写法,但是对于发帖人的例子来说,是一种针对性的优化。 因为你会发现发的贴子中,其实都是key为1的值进行更改。map仅仅是作为仓库使用。所以根本不需要对map进行更新操作,只读操作就好了。

saberlong
saberlong · #27 · 5年之前

通过例二的优化,能减少map的处理。同时还减少对象,减少gc. 当然对比发帖人int这种值类型来说,反而增加列gc扫描成本。

xxxcat
xxxcat · #28 · 5年之前

load, store 是安全的,但load和store组合成的 add, sub 不安全,你这程序在任何语言中都不对的,不仅仅是Golang

anko
anko · #29 · 5年之前

28楼 @xxxcat 我主要是对sync.map不大熟悉,你看下expvar.Map,我以为他是expvar.Map,所以才会出现这种代码! expvar.Map是不用加锁的,内部已经加了,也就是读写互斥!他才是真的线程安全,而sync.map仅仅是操作原子性,但他并不是线程安全的!用了sync.map的地方很多都需要继续用锁!否则达不到用户级别的线程安全

jarlyyn
jarlyyn · #30 · 5年之前

sync及其子包主要解决了3个问题:线程安全,锁,原子操作。

首先,你的程序没有报错,只是数据与你预期不一致,说明并发没有问题。并发解决的是读的同时写的问题。你的代码压根就没有线程不安全

其次,你现在需要的是数据的递增递减功能,这是原子操作,也就是cas的范畴。具体的包是sync/atomic/ 这个包

线程安全和原子操作是完全不同的两个范畴。

你搞错了自己的需求,用错包了。

jarlyyn
jarlyyn · #31 · 5年之前
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var wg sync.WaitGroup

func main() {
    var m =map[int]*int64{}
    for i := 0; i < 65536; i++ {
        var value int64=1
        m[i]=&value
   }
    wg.Add(2)
    go Add(&m)
    go sub(&m)

    wg.Wait()
    fmt.Println(*(m[1]))
}

func Add(m *map[int]*int64) {
    for i := 0; i < 10000; i++ {
        atomic.AddInt64((*m)[1],1)
    }
    wg.Done()
}

func sub(m *map[int]*int64) {
    for i := 0; i < 10000; i++ {
        atomic.AddInt64((*m)[1],-1)
    }
    wg.Done()
}
jarlyyn
jarlyyn · #32 · 5年之前

在觉得自己发现一个大bug之前,不妨先确认下,自己是不是用错了工具。

jarlyyn
jarlyyn · #33 · 5年之前

再说的更清楚写。

你的代码里达不到你预期的不是sync.map

而是+=

你需要用atomic 的add函数代替+=

anko
anko · #34 · 5年之前
jarlyynjarlyyn #33 回复

再说的更清楚写。 你的代码里达不到你预期的不是sync.map 而是+= 你需要用atomic 的add函数代替+=

我以为sync.map的功能类似expvar.Map,结果不是,所以这不全算是我的锅,整个网络没有几个人用sync.map写过线程并发的例子,看很多人都说sync.map是并发安全的,对,这个描述是不大准确的,如果真要说,只能说sync.map的各个api操作是并发安全的,但是,千万不要说sync.map这个对象或者类型是并发安全的!类似expvar.Map这个对象或者类型才是真的并发安全的!当然expvar.Map下面的所有操作都还是并发安全的!只要是设计到这个expvar.Map对象都会是并发安全的!其实本质是锁的粒度有大有小,expvar.Map刚好是大粒度锁,而sync.map只是锁住的是方法,而不是当前的expvar.Map对象,所以,我之所以会出现这种错误,恐怕跟我了解expvar.Map这个对象不多,目前网上大部分人都是研究sync.map对象而不是expvar.Map,因此我才会误将sync.map当做expvar.Map来使用了,至于atomic包下面的对象实现操作原子性,虽然不是锁,但是其实跟锁类似,比锁性能高点!所以我觉得go社区做的还不够!关于go的技术文章也经常是关于GC如何优化,G程,channel等等,不怎么全面覆盖到go的方方面面,比如我求一个sync.map的真实应用场景,恐怕应用场景不多!

anko
anko · #35 · 5年之前

33楼 @jarlyyn 就比如我自己测试过,我发现如下性能比例:expvar.Map :加锁的map : sync.map = 0.75 : 1 : 3,如果不是我去探究这个问题,恐怕go社区没个人知道的吧?文章标题确实是我很生气调试了2天都没能提高性能下写出的。我为此感到尴尬!不是我希望是博眼球,而是每次问个问题希望探讨一下都很少人来探讨!无奈之下的方法!请谅解!下次我不会这样取标题了!

jarlyyn
jarlyyn · #36 · 5年之前
ankoanko #34 回复

#33楼 @jarlyyn 我以为sync.map的功能类似expvar.Map,结果不是,所以这不全算是我的锅,整个网络没有几个人用sync.map写过线程并发的例子,看很多人都说sync.map是并发安全的,对,这个描述是不大准确的,如果真要说,只能说sync.map的各个api操作是并发安全的,但是,千万不要说sync.map这个对象或者类型是并发安全的!类似expvar.Map这个对象或者类型才是真的并发安全的!当然expvar.Map下面的所有操作都还是并发安全的!只要是设计到这个expvar.Map对象都会是并发安全的!其实本质是锁的粒度有大有小,expvar.Map刚好是大粒度锁,而sync.map只是锁住的是方法,而不是当前的expvar.Map对象,所以,我之所以会出现这种错误,恐怕跟我了解expvar.Map这个对象不多,目前网上大部分人都是研究sync.map对象而不是expvar.Map,因此我才会误将sync.map当做expvar.Map来使用了,至于atomic包下面的对象实现操作原子性,虽然不是锁,但是其实跟锁类似,比锁性能高点!所以我觉得go社区做的还不够!关于go的技术文章也经常是关于GC如何优化,G程,channel等等,不怎么全面覆盖到go的方方面面,比如我求一个sync.map的真实应用场景,恐怕应用场景不多!

sync.map的确是线程并发的,而且是并发安全的。

你对于并发安全和原子性的概念弄反了。

sync.map的典型场景是缓存/系统全局设置。每次设和取之前没有逻辑关系。

另外,sync.Map的性能测试和实现原理实际上网上很多。

而且sync.Map的代码注释写的非常清楚

// Map is like a Go map[interface{}]interface{} but is safe for concurrent use // by multiple goroutines without additional locking or coordination. // Loads, stores, and deletes run in amortized constant time. // // The Map type is specialized. Most code should use a plain Go map instead, // with separate locking or coordination, for better type safety and to make it // easier to maintain other invariants along with the map content. // // The Map type is optimized for two common use cases: (1) when the entry for a given // key is only ever written once but read many times, as in caches that only grow, // or (2) when multiple goroutines read, write, and overwrite entries for disjoint // sets of keys. In these two cases, use of a Map may significantly reduce lock // contention compared to a Go map paired with a separate Mutex or RWMutex.

大部分情况应该使用锁和map,sync.MAp适用两个场景

1.偶尔写入大量读取 2.多个进程会经常读写/覆盖不相关的节点的数据。

至于你说map加锁,只要看一下sync.Map的结构就知道,sync.map就是一个锁,一个 map,一个atomic.Value实现的,本来就是根据特殊场景规划好的锁+map

anko
anko · #37 · 5年之前
jarlyynjarlyyn #36 回复

#34楼 @anko sync.map的确是线程并发的,而且是并发安全的。 你对于并发安全和原子性的概念弄反了。 sync.map的典型场景是缓存/系统全局设置。每次设和取之前没有逻辑关系。 另外,sync.Map的性能测试和实现原理实际上网上很多。 而且sync.Map的代码注释写的非常清楚 // Map is like a Go map[interface{}]interface{} but is safe for concurrent use // by multiple goroutines without additional locking or coordination. // Loads, stores, and deletes run in amortized constant time. // // The Map type is specialized. Most code should use a plain Go map instead, // with separate locking or coordination, for better type safety and to make it // easier to maintain other invariants along with the map content. // // The Map type is optimized for two common use cases: (1) when the entry for a given // key is only ever written once but read many times, as in caches that only grow, // or (2) when multiple goroutines read, write, and overwrite entries for disjoint // sets of keys. In these two cases, use of a Map may significantly reduce lock // contention compared to a Go map paired with a separate Mutex or RWMutex. 大部分情况应该使用锁和map,sync.MAp适用两个场景 1.偶尔写入大量读取 2.多个进程会经常读写/覆盖不相关的节点的数据。 至于你说map加锁,只要看一下sync.Map的结构就知道,sync.map就是一个锁,一个 map,一个atomic.Value实现的,本来就是根据特殊场景规划好的锁+map

确实,官方说的很对,你说的也有道理,不过我们不冲突,我表达的是锁的粒度有大有小,而我的业务刚好既需要插入也需要更改,所以就不大适合这个sync.map,因为sync.map的粒度太小了,某些场合用它确实可以,如果经常需要修改的点,恐怕用这个sync.map就不适合了!

anko
anko · #38 · 5年之前
jarlyynjarlyyn #36 回复

#34楼 @anko sync.map的确是线程并发的,而且是并发安全的。 你对于并发安全和原子性的概念弄反了。 sync.map的典型场景是缓存/系统全局设置。每次设和取之前没有逻辑关系。 另外,sync.Map的性能测试和实现原理实际上网上很多。 而且sync.Map的代码注释写的非常清楚 // Map is like a Go map[interface{}]interface{} but is safe for concurrent use // by multiple goroutines without additional locking or coordination. // Loads, stores, and deletes run in amortized constant time. // // The Map type is specialized. Most code should use a plain Go map instead, // with separate locking or coordination, for better type safety and to make it // easier to maintain other invariants along with the map content. // // The Map type is optimized for two common use cases: (1) when the entry for a given // key is only ever written once but read many times, as in caches that only grow, // or (2) when multiple goroutines read, write, and overwrite entries for disjoint // sets of keys. In these two cases, use of a Map may significantly reduce lock // contention compared to a Go map paired with a separate Mutex or RWMutex. 大部分情况应该使用锁和map,sync.MAp适用两个场景 1.偶尔写入大量读取 2.多个进程会经常读写/覆盖不相关的节点的数据。 至于你说map加锁,只要看一下sync.Map的结构就知道,sync.map就是一个锁,一个 map,一个atomic.Value实现的,本来就是根据特殊场景规划好的锁+map

非常感谢,让我学到许多东西,不过关于原子,锁以及锁的粒度我应该没表达错误,

saberlong
saberlong · #39 · 5年之前
jarlyynjarlyyn #30 回复

sync及其子包主要解决了3个问题:线程安全,锁,原子操作。 首先,你的程序没有报错,只是数据与你预期不一致,说明并发没有问题。并发解决的是读的同时写的问题。你的代码压根就没有线程不安全 其次,你现在需要的是数据的递增递减功能,这是原子操作,也就是cas的范畴。具体的包是sync/atomic/ 这个包 线程安全和原子操作是完全不同的两个范畴。 你搞错了自己的需求,用错包了。

线程安全这点,你和anko理解上应该不同。 你说的并发安全是从数据内存访问这点出发的。Add和Sub方法由于访问的int是值类型,所以从内存访问上并不存在竞争问题。 而anko所说的并发安全应该是资源竞争,更倾向于方法的线程安全。从这个角度,写的Add和Sub是线程非安全的。

anko
anko · #40 · 5年之前
saberlongsaberlong #39 回复

#30楼 @jarlyyn 线程安全这点,你和anko理解上应该不同。 你说的并发安全是从数据内存访问这点出发的。Add和Sub方法由于访问的int是值类型,所以从内存访问上并不存在竞争问题。 而anko所说的并发安全应该是资源竞争,更倾向于方法的线程安全。从这个角度,写的Add和Sub是线程非安全的。

大佬发话了,是的,我的代码是要实现线程安全,我要保证几个api是原子性发生,而不是保证单个api线程安全!

xxxcat
xxxcat · #41 · 5年之前
ankoanko #29 回复

28楼 @xxxcat 我主要是对sync.map不大熟悉,你看下expvar.Map,我以为他是expvar.Map,所以才会出现这种代码! expvar.Map是不用加锁的,内部已经加了,也就是读写互斥!他才是真的线程安全,而sync.map仅仅是操作原子性,但他并不是线程安全的!用了sync.map的地方很多都需要继续用锁!否则达不到用户级别的线程安全

sync.Map 与expvar.Map 面向的问题有所不同,前者是为了提供基础的线程安全容器,后者是要面向具体的需求,像C++、Java也有提供这类基础容器,它们都能保证容器本身的安全,但是都不保证容器中的元素并发安全。

容器的并发安全,容器中元素的并发安全,这二者不是一回事

anko
anko · #42 · 5年之前
saberlongsaberlong #39 回复

#30楼 @jarlyyn 线程安全这点,你和anko理解上应该不同。 你说的并发安全是从数据内存访问这点出发的。Add和Sub方法由于访问的int是值类型,所以从内存访问上并不存在竞争问题。 而anko所说的并发安全应该是资源竞争,更倾向于方法的线程安全。从这个角度,写的Add和Sub是线程非安全的。

所以,我个人觉得,就是不能说sync.map这个对象是线程安全的!应该说这个对象下的所有方法都是独立的线程安全的!但是他们组合在一起就不是线程安全的!不同的是,expvar.Map这个对象锁的粒度大点,他确实可以说是线程安全的!不关是组合这个对象下的所有方法,还是单个操作,都是线程安全的!这点跟sync.map是有挺大区别的!我确实在没发这个帖子之前我以为sync.map是类似于expvar.Map一样都是线程安全的,我没想到sync.map的锁粒度小点,只对单个的增删改查做了锁,但是当将增删改查混在一起写时候组合起来并不是线程安全的!所以感谢各位大佬了!学习中,让我们一起进步!

anko
anko · #43 · 5年之前
xxxcatxxxcat #41 回复

#29楼 @anko sync.Map 与expvar.Map 面向的问题有所不同,前者是为了提供基础的线程安全容器,后者是要面向具体的需求,像C++、Java也有提供这类基础容器,它们都能保证容器本身的安全,但是都不保证容器中的元素并发安全。 容器的并发安全,容器中元素的并发安全,这二者不是一回事

其实,锁分为2种,一种锁属性,一种锁对象,sync.map的那种就是将各个方法的执行强制限制为单个线程,而expvar.Map则是将这个对象的访问和修改统一限制为单个线程,sync.map锁住的是对象下的独立的每个方法,而expvar.Map锁住的是这个对象,自然就是比sync.map的锁粒度要大点!

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传