V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
SmaliYu
V2EX  ›  C++

多线程操作非线程安全 map 容器,读写里面的元素是否也需要加锁进行操作?

  •  
  •   SmaliYu · 2020-06-19 12:47:40 +08:00 · 5338 次点击
    这是一个创建于 1400 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我一直有这样的疑问,比如有一个 Map<String, Student> map ;几个线程同时对这个 map 进行添加,删除,修改里面 Student 里的属性值,是不是这三种操作都需要锁住? 我很明确操作对这个 map 进行添加,删除,获取 size 都是需要锁住的,那么修改里面元素是不是也需要呢? 这个问题主要针对 C++和 Java 。

    18 条回复    2020-08-13 21:04:40 +08:00
    nightwitch
        1
    nightwitch  
       2020-06-19 13:08:16 +08:00
    只说 C++
    修改元素->加锁,不加的话如果你有两个线程同时修改到了一个 student 那就傻逼了。
    增加元素->应该可以不用,map 保证 insert 不会使其他迭代器失效
    删除元素->加,虽然 map 保证只影响被删除的元素,但是如果另一个线程在操作被删除的元素就会碰见问题。
    xiangyuecn
        2
    xiangyuecn  
       2020-06-19 13:13:16 +08:00
    既然非线程安全,读写都加锁完事。据我所知:c++不知道,java 、c#的非线程安全 map 内部存在 while(true)类似实现,并发操作存在一定概率会造成死循环,造成程序假死 内存暴涨。
    xuanbg
        3
    xuanbg  
       2020-06-19 13:13:39 +08:00
    看你读之后是不是还要写,要写就要锁,只读不需要锁。
    ccpp132
        4
    ccpp132  
       2020-06-19 13:27:57 +08:00
    读写都要,所有操作都要。
    shuax
        5
    shuax  
       2020-06-19 13:34:04 +08:00
    加读写锁
    SmaliYu
        6
    SmaliYu  
    OP
       2020-06-19 13:54:10 +08:00
    @nightwitch 谢谢您,解决了我多年以来的疑惑,再次谢谢您……
    killmojo
        7
    killmojo  
       2020-06-19 13:59:46 +08:00
    可以参考数据库的四个隔离级别,从需求出发确定什么情况要加锁,比如出现数据只是浏览,出现幻象读无所谓,那就不必读也加锁。
    现在很多做电子表格文档协同的也是类似问题,从需求角度出发做好需求的限制,技术上实现就简单一些。
    lxk11153
        8
    lxk11153  
       2020-06-19 14:45:23 +08:00
    感觉你没描述清楚,是如下吗?
    条件 1. 非线程安全 map 2. 我参考 java hashMap<String, Student>的 3. 锁的颗粒是针对 map 这个实例
    1. map.put - 加锁 https://juejin.im/post/5a66a08d5188253dc3321da0
    2. map.remove - 不清楚,应该要吧?
    3. map.get - 需要吗?
    4. map.size - 需要吗? see java.util.HashMap#size()
    ---- 但你看 java.util.Collections#synchronizedMap(Map<K, V>) 看似把所有方法都加锁了,保证不出错吧或者它这个 synchronized 就是指同步所有
    5. student.name="new" - 不需要

    ps: 我突然想到 hashCode 的问题, 假设 Student 使用了 https://projectlombok.org/features/EqualsAndHashCode
    ,map=hashMap<Student, Object>
    Student s1=new Student("name");
    map.put(s1, "name");
    s1.name="new"; // 会引起 hashCode 变化吧?
    map.put(s1, "new"); // map.size 变成 2 了吧,哈哈哈
    BQsummer
        9
    BQsummer  
       2020-06-19 14:51:53 +08:00
    生产教训:map 的 put 要加锁,扩容时导致数据丢失( java )
    wysnylc
        10
    wysnylc  
       2020-06-19 15:05:57 +08:00
    @BQsummer #8 用的 HashMap 而不是 ConcurrentHashMap?
    lniwn
        11
    lniwn  
       2020-06-19 15:06:02 +08:00
    @nightwitch #1 纠正一点,虽然 add 操作不会使迭代器失效,但是会修改节点之间的关系,涉及到红黑树的自平衡,所以任何 update 操作都需要加锁。可以使用读写锁,read 操作加读锁,update 操作加写锁。
    mind3x
        12
    mind3x  
       2020-06-19 15:15:08 +08:00 via Android
    @SmaliYu 他说的是错的,insert 不使其他迭代器失效本来前提就是单线程。多线程读写完全不是一回事
    CRVV
        13
    CRVV  
       2020-06-19 15:47:11 +08:00
    这种事情要看文档的


    在 C++ 里,容器上的 const member function 是线程安全的。
    然后在这里面 https://en.cppreference.com/w/cpp/container/map

    const T& at( const Key& key ) const; 是线程安全的
    T& operator[]( Key&& key ); 不是线程安全的,所以从 specification 的角度来说,有两个线程都在 m[key] 也是错的。
    insert 当然不是线程安全的,明显不可能

    必须所有线程上都只使用 const member function 才没有 data race,所以实际情况通常是这些操作全都要加锁。


    Java 的 HashMap,https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html

    If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.)

    添加删除都需要加锁,修改一个已经存在的 entry 不需要。
    BQsummer
        14
    BQsummer  
       2020-06-19 18:19:49 +08:00
    @wysnylc HashMap, 对象丢到线程池的多个 task 里了,老代码背了个 4 级事故
    wysnylc
        15
    wysnylc  
       2020-06-19 18:24:49 +08:00
    @BQsummer #13 java8 之前的 HashMap 扩容会导致死循环(头插法),java8 之后才是丢数据(尾插法)
    解决 map 并发方案简单粗暴,改成 ConcurrentHashMap 或者 ConcurrentSkipListMap(推荐)
    Wirbelwind
        16
    Wirbelwind  
       2020-06-19 18:28:47 +08:00
    c u d 都要加锁,不然会发生各种你能想到和想不到的情况
    snnn
        17
    snnn  
       2020-06-20 12:19:32 +08:00
    No.
    hardwork
        18
    hardwork  
       2020-08-13 21:04:40 +08:00
    c++ std::map 多线程肯定要加锁啊,修改也要加锁啊,增加肯定也要加锁啊.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1064 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:04 · PVG 07:04 · LAX 16:04 · JFK 19:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.