如何用shared_ptr减少锁的争用

在并发环境下锁的使用是家常便饭, 如何减少锁的使用是优化程序性能的一个方面. c++11里面新增了智能指针std::shared_ptr, 这个东西也许能给我们带来些启发. shared_ptr的一个特性是当引用计数为0时,它所拥有的堆内存会被自动释放. 利用这个特性我们可以做点实用的功能, 如下程序:

#include <assert.h>
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>

std::shared_ptr<int> kNumPtr(new int(0));
std::mutex kmtx;

std::shared_ptr<int> getSharedPtr()
{
        kmtx.lock();
        std::shared_ptr<int> ptr = kNumPtr;
        kmtx.unlock();
        return ptr;
}

void dosomething(std::shared_ptr<int> ptr)
{
        std::cout << "value: " << *ptr << std::endl;
}

int main()
{

        auto threadProc = [&](){
                        for(size_t i = 0; i < 100; ++i)
                        {
                                kmtx.lock();
                                if(!kNumPtr.unique()){
                                        kNumPtr.reset(new int(*kNumPtr));
                                }
                                assert(kNumPtr.unique());
                                *kNumPtr = *kNumPtr + 1;
                                kmtx.unlock();
                                std::this_thread::sleep_for(std::chrono::milliseconds(1));
                        }
                };

        std::thread t1(threadProc);
        std::thread t2(threadProc);
        std::thread t3(
                [&](){
                        for(size_t i = 0; i < 100000; ++i)
                        {
                                std::shared_ptr<int> ptr = getSharedPtr();
                                dosomething(ptr);
                        }
                }
        );

        t1.join();
        t2.join();
        t3.join();

        std::cout << "kNumPtr‘s value: " << *kNumPtr << std::endl;
        assert(*kNumPtr = 200);
        assert(kNumPtr.unique());
}

  我们一共启动了三个线程, 这个程序模拟的场景是读的频率远远大于写的频率. 两个写线程模拟对共享数据的修改, 一个读线程用来模拟高频读的行为. dosomething模拟对数据的操作. 通常我们的程序需要在读操作的时候加上锁, 然而这里却只是加了一个锁用来做拷贝智能指针的操作,临界区的长度基本可以忽略, 如果dosomething的耗时很长, 比如服务器网络编程中通常的IO读写, 那么这个锁的开销其实是很大的, 对其它线程共享资源的访问性能延迟很大. 而如果换成现在的代码, 其代价几乎可以忽略.

  再看写线程, 其实写线程用了copy_on_write技术, 将旧有的共享资源拷贝到新的地址空间上, 比较重要的一点是这个旧有的资源其实在reset之后 其引用计数(use_count)的值其实为0, 如果在读线程中处理完毕之后, 操作系统会自动将这份旧有内存进行释放, 这点才是真正有意思的地方.

  程序地址:https://github.com/xiaopeifeng/CodeTricks/blob/master/shared_ptr_copyonwrite.cc

时间: 2024-12-24 10:28:57

如何用shared_ptr减少锁的争用的相关文章

双缓冲队列来减少锁的竞争

双缓冲队列来减少锁的竞争 在日常的开发中,日志的记录是必不可少的.但是我们也清楚对同一个文本进行写日志只能单线程的去写,那么我们也经常会使用简单lock锁来保证只有一个线程来写入日志信息.但是在多线程的去写日志信息的时候,由于记录日志信息是需要进行I/O交互的,导致我们占用锁的时间会加长,从而导致大量线程的阻塞与等待. 这种场景下我们就会去思考,我们该怎么做才能保证当有多个线程来写日志的时候我们能够在不利用锁的情况下让他们依次排队去写呢?这个时候我们就可以考虑下使用双缓冲队列来完成. 所谓双缓冲

高并发下减少锁竞争

1.减少锁的持有时间,将不需要锁的操作从同步代码块的移除. //可以优化的代码 class AttributeStore{ private final Map<String,String> attributes=new HashMap<String,String>(); public synchronized boolean userLocationMatches(String username,String regex){ String key="user."

HashMap并发出现死循环 及 减少锁的竞争

线程不安全的HashMap, HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,查找时会陷入死循环. https://www.cnblogs.com/dongguacai/p/5599100.html https://coolshell.cn/articles/9606.html 减少锁的竞争3种方法: (1)减少锁的持有时间(缩小锁的范围) (2)降低锁的请求频率(降低锁的粒度) (3)放弃使用独占锁,使用并发容器,原子变量,读

如何用程序来锁电脑 (Lock)

如何写程序来控制 Windows Lock ( 锁住电脑 ) 方法 1 : ' 用 Shell 函数调用 Rundll32 来做 LockWorkStation Shell("rundll32 user32.dll,LockWorkStation") 方法 2 : ' Call API Private Declare Function LockWorkStation Lib "user32.dll" () As Long ' 声明 LockWorkStation'

利用双缓冲队列来减少锁的竞争

在日常的开发中,日志的记录是必不可少的.但是我们也清楚对同一个文本进行写日志只能单线程的去写,那么我们也经常会使用简单lock锁来保证只有一个线程来写入日志信息.但是在多线程的去写日志信息的时候,由于记录日志信息是需要进行I/O交互的,导致我们占用锁的时间会加长,从而导致大量线程的阻塞与等待. 这种场景下我们就会去思考,我们该怎么做才能保证当有多个线程来写日志的时候我们能够在不利用锁的情况下让他们依次排队去写呢?这个时候我们就可以考虑下使用双缓冲队列来完成. 所谓双缓冲队列就是有两个队列,一个是

无锁编程:采用不可变类减少锁的使用

很多的同学很少使用.或者干脆不了解不可变类(Immutable Class).直观上很容易认为Immutable类效率不高,或者难以理解他的使用场景.其实不可变类是非常有用的,可以提高并行编程的效率和优化设计.让我们跳过一些宽泛的介绍,从一个常见的并行编程场景说起: 假设系统需要实时地处理大量的订单,这些订单的处理依赖于用户的配置,例如用户的会员级别.支付方式等.程序需要通过这些配置的参数来计算订单的价格.而用户配置同时被另外一些线程更新.显然,我们在订单计算的过程中保持配置的一致性. 上面的例

聊聊高并发(六)实现几种自旋锁(一)

在聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响 我们了解了处理器缓存一致性协议的原理,并且提到了它对并发编程的影响,"多个线程对同一个变量一直使用CAS操作,那么会有大量修改操作,从而产生大量的缓存一致性流量,因为每一次CAS操作都会发出广播通知其他处理器,从而影响程序的性能." 这一篇我们通过两种实现自旋锁的方式来看一下不同的编程方式带来的程序性能的变化. 先理解一下什么是自旋,所谓自旋就是线程在不满足某种条件的情况下,一直循环做某个动作.所以对于自旋锁来锁,当线程在没有获

SqlServer常见问题及案例(性能+维护)

缺少索引 案例:某次线上有很多接口请求失败,服务器上发现CPU使用率很高.通过活动分析器(最近耗费大量资源的查询)发现有几个查询的CPU时间(毫秒/秒)达到3000以上.推测是查询没有利用索引导致的,通过在相关表上加索引解决了问题. 分析:大部分性能问题都是因为缺失索引或索引失效导致的(书签查找).此次问题是由于基础表(配置表)之间的连接没有索引,当随着配置的数据越来越多,查询计算量会越来越大,当数据量达到一定量级(几千至几万)且并发比较高(几千)问题就会显现出来.这些问题可以在写查询时建立索引

mysql的mvcc(多版本并发控制)

mysql的mvcc(多版本并发控制) 我们知道,mysql的innodb采用的是行锁,而且采用了多版本并发控制来提高读操作的性能. 什么是多版本并发控制呢 ?其实就是在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号, 而每一个事务在启动的时候,都有一个唯一的递增的版本号. 1.在插入操作时 : 记录的创建版本号就是事务版本号. 比如我插入一条记录, 事务id 假设是1 ,那么记录如下:也就是说,创建版本号就是事务版本号. id name create version delete