【问题标题】:Is it thread safe to call ruby constants with a stabby lambda?使用 stabby lambda 调用 ruby​​ 常量是否线程安全?
【发布时间】:2019-09-17 03:05:38
【问题描述】:

使用 stabby lambda 或 proc 调用 ruby​​/rails CONSTANTS 是否线程安全?

我知道在 ruby​​ 社区中不鼓励将常量定义为动态方法,因为常量定义本身不是线程安全的,并且可能像全局变量一样受到竞争条件的影响。

但是,如果您定义一个常量来接收一个 stabby lambda 或 proc,这会使代码线程安全吗?

例如这篇博文 on defining constants using procs/lambda's 建议定义为 lambda 和 procs 的常量可以被信任以每次都可靠地唯一调用:

class Foo
  LAMBDA = lambda {Time.now}
  PROC = proc {Time.now}
  STABBYLAMBDA = ->{Time.now}
  BEGINBLOCK = begin Time.now end

  def Foo.the_time
    Time.now
  end
  METHODBLOCK = Foo.the_time
end

Foo::LAMBDA[]
# => 2019-09-16 22:39:51 -0700
Foo::LAMBDA[]
# => 2019-09-16 22:39:53 -0700
Foo::PROC[]
# => 2019-09-16 22:39:55 -0700 
Foo::PROC[]
# => 2019-09-16 22:39:57 -0700 
Foo::STABBYLAMBDA[]
# => 2019-09-16 22:39:59 -0700 
Foo::STABBYLAMBDA[]
# => 2019-09-16 22:40:01 -0700 
Foo::BEGINBLOCK
# => 2019-09-16 22:39:51 -0700
Foo::BEGINBLOCK
# => 2019-09-16 22:39:51 -0700
Foo::METHODBLOCK
# => 2019-09-16 22:39:51 -0700 
Foo::METHODBLOCK
# => 2019-09-16 22:39:51 -0700

但这是否也扩展到线程安全?


例如在这段代码中,我有一个 Rails 服务对象,它在多线程 Sidekiq 中运行,它提取用于上传 CSV 的数据:


class ExportReferralData < ServiceObject


  EXPORT_COLUMNS = ["Referrer_phone_number", "Referrer_ID", "Referee_phone_number", "Referee_ID", "Date_Approved"].freeze

  EXPORT_ROW = ->(referral){
    [referral.customer.identifier, referral.customer.accountable.internal_identifier, referral.friend.identifier, referral.friend.accountable.internal_identifier, referral.approved_at.strftime("%Y%m%d%H%M")]
  }


  def initialize
  end

  def call
    data = data_for_csv( Referral.all )

    UploadFile.put(file_path) do |file|
      create_csv(file, data)
    end
  end

  private

  def data_for_csv(active_record_query_result)
    active_record_query_result.inject([]) do |result, item|
      result << EXPORT_ROW.call(item)
    end
  end


  def create_csv(file, export_data)
    CSV(file, headers:true, force_quotes: true) do |csv|
      csv << EXPORT_COLUMNS
      export_data.each do |export_row|
        csv << export_row
      end
    end
    file.rewind
  end

  def file_path
    "upload/APPROVED_#{Time.current.strftime("%Y%m%d%H%M")}.csv"
  end
end

这个多线程代码将 EXPORT_ROW 定义为一个常量,它等于一个 stabby lambda(不是一个 proc),然后它采用它传递的任何 referral ActiveRecord 对象来动态构建每个 CSV 行。 lambda 中的这个局部变量然后调用 ActiveRecord 查询。

从风格上讲,我喜欢这段代码的外观,但我想确保我在这里使用 EXPORT_ROW.call(item) 实际上是线程安全的,尤其是在调用 ActiveRecord 查询等方法时。

非常感谢任何帮助!

【问题讨论】:

  • 不知道,但你为什么不直接使用 Mutex 来确定呢?
  • 在这里使用互斥锁会是什么样子?
  • “定义为 lambda(但不是 procs)的常量可以被信任每次都可靠地唯一调用”——我不知道这意味着什么,也无法在文章中找到任何提及。您使用 proc 得到两个相同结果的事实只是意味着您执行它们的速度太快了,所以没有时间……呃,时间……来改变。这篇文章也完全不是关于线程安全的。所有Proc 对象(无论是通过lambda、proc 还是-&gt; 获得)都是不可变的,因此它们对线程安全的影响为零(尽管它们的内容当然可以是线程安全的)。跨度>
  • @Amadan 感谢您指出这一点,我再次在本地运行代码并更新了我的示例。我只确认Foo::BEGINBLOCK 和Foo::METHODBLOCK 无法重新调用Time.now。
  • 两者都不是Proc;它们是执行您给它们的表达式的结果(在本例中为Date)。

标签: ruby-on-rails ruby multithreading csv constants


【解决方案1】:

关于 lambda 是否是线程安全的,答案与方法相同;这取决于你在 lambda 中做了什么。只要您不在多个线程中对共享状态(例如同一个对象)进行修改,就可以了。

创建Foo 类的实例时,会创建LAMBDA 常量,并使用= 右侧的lambda 对象进行初始化。随后,LAMBDA 的每个引用都会返回该 lambda 实例。

从多个线程引用LAMBDA 是安全的,因为它只是返回对象。然而,虽然 引用 LAMBDA 可能是安全的,但这并不一定意味着 执行 它是安全的。这取决于 lambda 的内容。如果 lambda 包含修改任何共享状态的代码,那可能是个问题。看起来您在 lambda 中使用的推荐对象只是在读取数据。应该没问题。

无论是传递给Thread.new的lambda、方法还是块都没有关系;重要的是该线程中正在执行什么代码。

我不明白的一件事是,如果call 方法正在获取所有推荐,Sidekiq 怎么知道如何将其划分为多个作业/线程?在我看来,这项工作将在一个线程中完整运行。在这种情况下,这个问题没有实际意义,不是吗?但是我对 Sidekiq 不熟悉,所以我可能错了。

其他几点:

  • 请不要使用[] 表示法来调用lambda。是的,它是合法的,但它会造成不必要的混淆和误导。最好使用.call 或.()(我最喜欢的)。
  • 当您说“一个等于 stabby lambda 的常量”时,您暗示 stabby lambda 对象不同于非 stabby lambda 对象。无论哪种方式,它都是一个 lambda 对象; stabby 只是一个用于创建它的缩写符号。
  • 同样,“定义为 lambda 和 procs 的常量可以被信任每次都可靠地唯一调用”揭示了对 lambda 或 proc 是什么的误解......这就像说 方法“可以被信任每次都可靠地唯一调用”; 当然可以。常量类似于变量,因为它是对对象的引用。它不会向它所指的事物添加任何功能。

如果您想进一步研究 lambda,我已经进行了一些演讲并撰写了一些关于它们的文章。以下是一些资源:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-10
    • 2019-06-27
    • 1970-01-01
    • 2018-03-12
    相关资源
    最近更新 更多