让我们假设我们有两个函数fct1
和fct2
:
fct1
呼叫fct2
,fct1
将应用程序中的某些对象O1
设置为状态A
,fct2
将应用程序中的某些对象O2
设置为状态B
。让我们假设以下约束必须始终成立:
01
处于A
状态,而02
处于B
状态),01
处于not(A)
状态,而02
处于not(B)
状态)。如果在通话过程中与fct1
会发生什么情况:
fct1
现在将某个对象01
设置为状态not(A)
,fct2
现在将某个对象02
设置为状态not(B)
。是否可以通过将01
设置为状态A
并将02
设置为状态not(B)
来“打破”约束?
我找到了这个答案: https://stackoverflow.com/a/20477763/9614866
如果递归函数重新定义了自己,则递归调用仍将在同一调用中进行,可能会继续到达同一正文。 [...] 更一般而言,Common Lisp允许编译器在同一文件中的函数之间生成有效的调用。因此,您通常必须考虑将正在运行的代码替换为模块级别而不是单个功能级别。如果功能A和B在同一个模块中,并且A调用B,则如果仅替换B而没有替换A,则A 可以继续调用旧的B(因为B内联到A中,或者因为A不会通过符号,但是会为B使用更直接的地址。您可以声明为非内联函数以抑制这种情况。
我的问题是:
01
设置为A
状态,而02
设置为not(B)
状态)?它有名字吗?如果“是”:
答案 0 :(得分:2)
以下是在多线程环境中您所描述的内容如何发生的示例:
(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))
如果您在定义(declaim (inline f2))
之前添加f2
并再次进行测试,则仍然会从 old执行来自 old f2
的代码 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))
它甚至可以在没有版本号的情况下运行,如果您先删除该软件包,然后使用相同的名称重新创建它,那么您将很难恢复。
某些地方可能还需要全局锁,您必须在应用程序中和更新期间都必须对此全局锁进行管理。