STL容器的线程安全

众所周知,STL容器不是线程安全的。

对于vector,即使写方(生产者)是单线程写入,但是并发读的时候,由于潜在的内存重新申请和对象复制问题,会导致读方(消费者)的迭代器失效。实际表现也就是招致了core dump。另外一种情况,如果是多个写方,并发的push_back(),也会导致core dump。

解法一:

加锁是一种解决方案,但是加std::mutex互斥锁确实性能较差。对于多读少写的场景可以用读写锁(也叫共享独占锁),来缓解。C++17引入了std::shared_mutex 。更多锁的种类可以阅读下面这篇回答,但是本回答的目的自然不是自我重复再次介绍一次锁的使用,请继续阅读解法二!

解法二:

更多的时候,其实可以通过固定vector的大小,避免动态扩容(无push_back)来做到lock-free!即在开始并发读写之前(比如初始化)的时候,给vector设置好大小。

struct Data {
...
};
vector<Data> v;
v.resize(1000);

注意是resize,不是reserve!

可能大家平时用reserve()比较多,顾名思义,reserve就是预留内存。为的是避免内存重新申请以及容器内对象的拷贝。

说白了,reserve()是给push_back()准备的!而resize除了预留内存以外,还会调用容器元素的构造函数,不仅分配了N个对象的内存,还会构造N个对象。从这个层面上来说,resize()在时间效率上是比reserve()低的。但是在多线程的场景下,用resize再合适不过。你可以resize好N个对象,多线程不管是读还是写,都是通过容器的下标访问【operator[]】来访问元素,不要push_back新元素。所谓的『写操作』在这里不是插入新元素,而是修改旧元素。

如果N的最大个数是可以预期的就直接设置就好,如果没办法预期就再把vector搞成ring buffer(环形队列)来缓解压力。可以给元素类加上成员变量标记当前的读写状态、是否被消费等等。

当然,你会说,如果B,C,D,E,F这个5个线程是等价的,要不停消费vector中的元素,会造成重复消费不?当然会。你可以把 队列头的下标定义程原子变量(std::atomic),尽管原子变量也需要做线程同步,但是比一般的锁开销要小很多啦。如果你想连原子变量也不用,有没有办法呢?有啊。那就给B,C,D,E,F分配不同的消费队列啊。

比如当前有5个读线程,那么每个线程就消费下标模5之后的某个固定结果的下标。比如:

B消费:051015、……
C消费:161116、……
D消费:271217、……
E消费:381318、……
F消费:491419、……

每个读线程各自维护自己当前消费的最新下标。这样做有啥问题没?也有,就是可能会导致不同的线程繁忙和等待的情况差异巨大:忙的忙死,闲的闲死。具体场景具体分析,总之,无论如何要控制住。不要让一个任务hang住整个线程。

关联容器的线程安全问题

vector是顺序容器,STL中还有一类关联容器其线程安全问题也不容小觑。比如map、unordered_map。我们可能会有这样一种场景:在并发环境下,收集一些Key-Value,存储在某一个公共的容器中。这里也谈一下不用锁的方案,当然做不到放之四海皆准。它有一些限制条件,只能看是否满足你的需要了。当有多个写线程对情况下,并发地插入 map/unordered_map都会引发core dump。对此,在某些场景下也可以避免加锁:如果全量的key有办法在并发之前就能拿到的,那么就对这个map,提前做一下insert。并发环境中如果只是修改value,而不是插入新key就不会core dump!不过如果你没办法保证多个写线程不会同时修改同一个key的value,那么可能存在value的覆盖。无法保证这点时,还是需要加锁。不过可以对key采取某种hash策略转成整型,然后进行分段加锁,减少一点锁冲突的概率,或者用一下CAS的策略。你还有什么好的思路,欢迎补充!

STL(Standard Template Library)是C++标准库中的一个重要组成部分,提供了一系列的容器类,如vector、list、map等。STL容器本身并不是线程安全的,也就是说,在多线程环境下同时对同一个容器进行读写操作可能会导致数据竞争和不确定的结果。 然而,C++标准库并没有提供原生的线程安全STL容器。如果需要在多线程环境下使用STL容器,可以考虑以下几种方式: 1. 互斥锁(Mutex):使用互斥锁来保护对STL容器的读写操作。在每次访问容器之前,先获取互斥锁,操作完成后释放锁。这样可以确保同一时间只有一个线程能够对容器进行操作,从而避免数据竞争。 2. 读写锁(Read-Write Lock):使用读写锁来实现对STL容器的读写操作。读写锁允许多个线程同时读取容器,但只有一个线程能够进行写操作。这样可以提高读取性能,但写操作仍然需要互斥保护。 3. 并发容器:一些第三方库或框架提供了线程安全STL容器的实现,如Intel TBB(Threading Building Blocks)库中的concurrent_vector、concurrent_map等。这些容器在内部使用了锁或其他机制来保证线程安全性。 需要注意的是,使用线程安全STL容器并不能完全解决多线程编程中的所有并发问题,仍然需要合理地设计和管理线程间的同步与通信。此外,使用锁或其他同步机制可能会引入额外的开销和复杂性,需要权衡使用的场景和性能需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值