【问题标题】:Function redefinitions during execution in lisp在 lisp 中执行期间的函数重定义
【发布时间】:2019-03-29 11:12:46
【问题描述】:

假设我们有两个函数fct1fct2

  • fct1 致电fct2
  • fct1 将应用程序中的某个对象O1 设置为A 状态,
  • fct2 将应用程序中的某个对象 O2 设置为状态 B

让我们假设以下约束必须始终为真:

  • 01 处于A 状态并且02 处于B 状态),
  • XOR(01 处于 not(A) 状态并且02 处于 not(B) 状态)。

如果打电话fct1会发生什么:

  • 重新定义:fct1 现在将一些对象 01 设置为状态 not(A)
  • 重新定义:fct2 现在将某些对象 02 设置为状态 not(B)

它可以通过将01 设置为状态A02 设置为状态not(B) 来“打破”约束吗?

我找到了这个答案: https://stackoverflow.com/a/20477763/9614866

如果递归函数重新定义自身,则在同一调用中仍要进行的递归调用可能会继续转到同一个主体。 [...] 更一般地说,Common Lisp 允许编译器在同一文件中的函数之间生成有效的调用。因此,您通常必须将运行代码的替换视为模块级别而不是单个功能级别。如果函数 A 和 B 在同一个模块中,并且 A 调用 B,那么如果您只是替换 B 而不替换 A,A可能继续调用旧 B(因为 B 内联到 A,或者因为A 不通过符号,而是为 B) 使用更直接的地址。你可以声明函数 notinline 来抑制这个。

我的问题是:

  • 会不会出现这种现象(即01设置为A状态,02设置为not(B)状态)?它有名字吗?

如果“是”:

  • 它是否依赖于实现?
  • 有没有办法强制正确的行为,例如通过内联函数?
  • 我可以使用哪些工具来测试这些功能是否以正确的方式工作?测试似乎很痛苦:我不知道如何在不更改基本源代码的情况下测试重新定义。
  • 如何检测可能出现此问题的代码部分?

【问题讨论】:

  • 除非对象可以同时处于状态 Anot(A),否则您的条件是矛盾的(不可能同时为真)。
  • 如果您愿意,我可以编辑问题。 :)

标签: common-lisp slime


【解决方案1】:

这是一个示例,说明您所描述的情况在多线程环境中如何发生:

(progn

  (defun f2 (o2)
    (setf (car o2) :b))

  (defun f1 (o1 o2)
    (setf (car o1) :a)
    ;; the sleep here is to increase the risk of data-race
    (sleep 3)
    (f2 o2))

  ;; call the functions in a separate thread
  (sb-thread:make-thread
   (lambda () 
     (let ((o1 (list 0))
           (o2 (list 0)))
       (f1 o1 o2)
       (print (list o1 o2)))))

  ;; in parallel, redefine f2, then f1
  (defun f2 (o2)
    (setf (car o2) :not-b))

  (defun f1 (o1 o2)
    (setf (car o1) :not-a)
    (f2 o2)))

3 秒后,REPL 打印出来

((:A) (:NOT-B))

如果在定义f2 之前添加(declaim (inline f2)) 并再次测试,则来自old f2 的代码仍会从old f1 执行,在线程内部,3秒后打印以下内容:

((:A) (:B)) 

更新函数f1的进一步调用给出:

((:NOT-A) (:NOT-B)) 

我可以使用哪些工具来测试这些功能是否以正确的方式工作?测试似乎很痛苦:我不知道如何在不更改基本源代码的情况下测试重新定义。

也许您正在使用新代码更新正在运行的服务器,并且您希望避免在加载定义时让服务器使用函数的部分重新定义。

与基础架构的所有其他方面一样,提前计划如何可靠地进行备份和更新(数据库、配置等)非常重要。

一种可能的方法是逐包更新事物。你可以在你的包后面加上一个版本号:

(in-package :web-0 ...)
(defun f2 () ...)
(defun f1 () ...)

;; new version
(in-package :web-1 ...)
(defun f2 () ...)
(defun f1 () ...)

当需要更新时,您可以编译和加载:web-1 的代码,而不会干扰正在运行的代码。然后您应该能够更改调用者以使用新的实现:

;; somewhere in the main server package
(handle-request (request)
   (web-0:handle request))

 ;; update it
(handle-request (request)
  (web-1:handle request))

它甚至应该在没有版本号的情况下工作,如果你先删除包然后用相同的名称重新创建它,但是你不能轻易恢复。

有些地方可能还需要一个全局锁,您必须在应用程序和更新期间管理它。

【讨论】:

  • 感谢您的打包方式和测试。 :p 所以看来我必须破解一种自定义方式,我希望有一种标准方式或一个我不知道的默默无闻的操作员来拯救我......确实是服务器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-26
  • 2016-08-21
  • 1970-01-01
相关资源
最近更新 更多