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

2020-06-19 12:47:40 +08:00
 SmaliYu

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

5591 次点击
所在节点    C++
18 条回复
nightwitch
2020-06-19 13:08:16 +08:00
只说 C++
修改元素->加锁,不加的话如果你有两个线程同时修改到了一个 student 那就傻逼了。
增加元素->应该可以不用,map 保证 insert 不会使其他迭代器失效
删除元素->加,虽然 map 保证只影响被删除的元素,但是如果另一个线程在操作被删除的元素就会碰见问题。
xiangyuecn
2020-06-19 13:13:16 +08:00
既然非线程安全,读写都加锁完事。据我所知:c++不知道,java 、c#的非线程安全 map 内部存在 while(true)类似实现,并发操作存在一定概率会造成死循环,造成程序假死 内存暴涨。
xuanbg
2020-06-19 13:13:39 +08:00
看你读之后是不是还要写,要写就要锁,只读不需要锁。
ccpp132
2020-06-19 13:27:57 +08:00
读写都要,所有操作都要。
shuax
2020-06-19 13:34:04 +08:00
加读写锁
SmaliYu
2020-06-19 13:54:10 +08:00
@nightwitch 谢谢您,解决了我多年以来的疑惑,再次谢谢您……
killmojo
2020-06-19 13:59:46 +08:00
可以参考数据库的四个隔离级别,从需求出发确定什么情况要加锁,比如出现数据只是浏览,出现幻象读无所谓,那就不必读也加锁。
现在很多做电子表格文档协同的也是类似问题,从需求角度出发做好需求的限制,技术上实现就简单一些。
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
2020-06-19 14:51:53 +08:00
生产教训:map 的 put 要加锁,扩容时导致数据丢失( java )
wysnylc
2020-06-19 15:05:57 +08:00
@BQsummer #8 用的 HashMap 而不是 ConcurrentHashMap?
lniwn
2020-06-19 15:06:02 +08:00
@nightwitch #1 纠正一点,虽然 add 操作不会使迭代器失效,但是会修改节点之间的关系,涉及到红黑树的自平衡,所以任何 update 操作都需要加锁。可以使用读写锁,read 操作加读锁,update 操作加写锁。
mind3x
2020-06-19 15:15:08 +08:00
@SmaliYu 他说的是错的,insert 不使其他迭代器失效本来前提就是单线程。多线程读写完全不是一回事
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
2020-06-19 18:19:49 +08:00
@wysnylc HashMap, 对象丢到线程池的多个 task 里了,老代码背了个 4 级事故
wysnylc
2020-06-19 18:24:49 +08:00
@BQsummer #13 java8 之前的 HashMap 扩容会导致死循环(头插法),java8 之后才是丢数据(尾插法)
解决 map 并发方案简单粗暴,改成 ConcurrentHashMap 或者 ConcurrentSkipListMap(推荐)
Wirbelwind
2020-06-19 18:28:47 +08:00
c u d 都要加锁,不然会发生各种你能想到和想不到的情况
snnn
2020-06-20 12:19:32 +08:00
No.
hardwork
2020-08-13 21:04:40 +08:00
c++ std::map 多线程肯定要加锁啊,修改也要加锁啊,增加肯定也要加锁啊.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/683001

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX