草庐IT

c++ - C++ 线程安全对象缓存的设计选项

coder 2024-02-20 原文

我正在用 C++ 编写一个用于数据缓存的模板库,其中可以进行并发读取和并发写入,但不是针对同一个键。该模式可以用以下环境来解释:

  1. 用于缓存写入的互斥锁。
  2. 缓存中每个键的互斥量。

这样,如果线程从缓存中请求一个键但不存在,则可以为该唯一键启动锁定计算。与此同时,其他线程可以检索或计算其他键的数据,但试图访问第一个键的线程会被锁定等待。

主要的约束是:

  1. 永远不要同时计算一个键的值。
  2. 可以同时计算 2 个不同键的值。
  3. 数据检索不得锁定其他线程以防止从其他键检索数据。

我的其他限制但已经解决的是:

  1. 固定(在编译时已知)基于 MRU(最近使用的)抖动的最大缓存大小。
  2. 通过引用检索(暗示互斥共享计数)

我不确定为每个键使用 1 个互斥锁是实现此目的的正确方法,但我没有发现任何其他有本质区别的方法。

您是否知道实现此功能的其他模式,或者您认为这是一个合适的解决方案?我不喜欢拥有大约 100 个互斥量的想法。 (缓存大小约为 100 个键)

最佳答案

你想锁定,你想等待。因此,某处应该有“条件”(如类 Unix 系统上的 pthread_cond_t)。

我建议如下:

  • 有一个全局互斥量,仅用于在映射中添加或删除键。
  • 映射将键映射到值,其中值是包装器。每个包装器都包含一个条件并可能包含一个值。设置值时会发出条件信号。

当线程希望从缓存中获取值时,它首先获取全局互斥锁。然后它在 map 中查找:

  1. 如果该键有一个包装器,并且该包装器包含一个值,那么该线程就有它的值并可以释放全局互斥量。
  2. 如果该键有一个包装器但还没有值,则这意味着其他线程当前正忙于计算该值。然后该线程会在该条件下阻塞,以便在它完成时被另一个线程唤醒。
  3. 如果没有包装器,则线程在映射中注册一个新的包装器,然后继续计算值。计算值时,它会设置值并发出条件信号。

在伪代码中,它看起来像这样:

mutex_t global_mutex
hashmap_t map

lock(global_mutex)
w = map.get(key)
if (w == NULL) {
    w = new Wrapper
    map.put(key, w)
    unlock(global_mutex)
    v = compute_value()
    lock(global_mutex)
    w.set(v)
    signal(w.cond)
    unlock(global_mutex)
    return v
} else {
    v = w.get()
    while (v == NULL) {
        unlock-and-wait(global_mutex, w.cond)
        v = w.get()
    }
    unlock(global_mutex)
    return v
}

pthreads术语中,lockpthread_mutex_lock()unlockpthread_mutex_unlock()unlock-and-waitpthread_cond_wait()signalpthread_cond_signal()unlock-and-wait 原子地释放互斥量并将线程标记为等待条件;当线程被唤醒时,互斥量会自动重新获取。

这意味着每个包装器都必须包含一个条件。这体现了您的各种要求:

  • 没有线程长时间持有互斥量(阻塞或计算值)。
  • 当要计算一个值时,只有一个线程执行此操作,其他希望访问该值的线程只需等待它可用即可。

请注意,当一个线程希望获取一个值并发现其他线程已经在忙于计算它时,线程最终会锁定全局互斥锁两次:一次在开始时,一次在该值可用时。一个更复杂的解决方案,每个包装器有一个互斥体,可以避免第二次锁定,但除非争用非常高,否则我怀疑这样做是否值得。

关于拥有多个互斥量:互斥量很便宜。互斥量基本上是一个 int,它的成本不超过用于存储它的四个左右字节的 RAM。注意 Windows 术语:在 Win32 中,我在这里所说的互斥量被认为是“互锁区域”;当 CreateMutex() 被调用时 Win32 创建的是完全不同的东西,它可以从几个不同的进程访问,并且因为它涉及到内核的往返而更昂贵。请注意,在 Java 中,每个单独的对象实例都包含一个互斥量,并且 Java 开发人员在这个问题上似乎并不过分脾气暴躁。

关于c++ - C++ 线程安全对象缓存的设计选项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2139724/

有关c++ - C++ 线程安全对象缓存的设计选项的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  4. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  5. ruby - 默认情况下使选项为 false - 2

    这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb

  6. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  7. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  8. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  10. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

随机推荐