InterSystems IRIS 中的锁
尽管LOCK(docs) 是 InterSystems IRIS 的基础部分,负责并发性,但开发者社区上关于它的讨论并不多。这是可以理解的,因为它是一个稳定且相当低级的命令。在本文中,我将举一个简单的例子,说明如何使用互操作性锁。在示例中,我们将有一个本地表,其中的引用数据由两个不同的进程使用:
- 从表中读取数据的实用功能(由生产中的各种 DTL/Rules 使用)
- 更新表的专用业务操作
这里的问题是,当业务操作更新表时(最糟糕的情况是进行完全重建),自定义函数将无法从表中获取数据,这将导致 DTL/规则处理出现问题。
锁可以帮助我们解决这个问题。具体方法如下:
- 实用程序将在获取数据前获得共享锁。任何数量的进程都可以持有共享锁,因此不会出现并发问题。一旦检索到数据,我们就会释放共享锁。
-
更新器业务操作会先使用共享锁,然后再释放独占锁。一旦某个进程获得独占锁,IRIS 就会保证其他进程无法获得同一资源上的锁。这样,当独占锁被持有时,实用程序就无法获取共享锁。一旦我们的业务操作完成对表的更新,它就会释放独占锁,允许实用程序访问表。
让我们开始吧
本地表
有点简单(在实际项目中作为 LUT 可能会更好),但我们的目的是展示锁是如何工作的,而不是构建一个复杂的表:
Class Lock.RefData Extends %Persistent
{
Property Value;
}请注意,表本身未作任何修改。
实用功能
下面是我们的实用程序:
Class Lock.CF Extends Ens.Rule.FunctionSet
{
ClassMethod GetRefData() As %String
{
$$$TRACE("Request Shared lock")
LOCK +^Lock.RefData("ref")#"S"
$$$TRACE("Gained Shared lock")
Try {
Set rs = ##class(%SQL.Statement).%ExecDirect(,"SELECT Value FROM Lock.RefData")
} Catch ex {
LOCK -^Lock.RefData("ref")#"S"
Return "EX - NO DATA"
}
if 'rs.%Next()
{
$$$TRACE("NO DATA, Releasing lock")
LOCK -^Lock.RefData("ref")#"S"
Quit "SQL - NO DATA"
}
Set val = rs.Value
$$$TRACE("Val: " _ val)
$$$TRACE("Releasing lock")
LOCK -^Lock.RefData("ref")#"S"
Quit val
}
}请注意,在运行 SQL 之前,我们会获取 ^Lock.RefData("ref") 上的共享锁,只有在本地变量中获取值后才会释放锁。
更新操作
接下来,让我们实现业务操作。它可以按计划运行,也可以手动运行。在我们的例子中,它将接受 Ens.StringContainer,并用一个整数指定模拟工作的秒数。
Class Lock.BO Extends Ens.BusinessOperation
{
Method OnMessage(pRequest As Ens.StringContainer, Output pResponse As Ens.Response) As %Status
{
Set sc = $$$OK
Try {
$$$TRACE("Request Shared lock")
LOCK +^Lock.RefData("ref")#"S"
$$$TRACE("Gained Shared lock")
Try {
$$$TRACE("Request Exclusive lock")
LOCK +^Lock.RefData("ref")
$$$TRACE("Gained Exclusive lock.")
Try {
$$$TRACE("Simulate the purge work")
&sql(DELETE FROM Lock.RefData)
Hang +pRequest.StringValue
Set obj = ##class(Lock.RefData).%New()
Set obj.Value = $zdt($h, 3, 1, 3)
Do obj.%Save()
$$$TRACE("Release exclusive lock on succesfull completion")
LOCK -^Lock.RefData("ref")
} Catch ex {
$$$TRACE("Release exclusive lock on error")
LOCK -^Lock.RefData("ref")
THROW ex // re-throw the exception after releasing lock
}
$$$TRACE("Release shared lock on successfull completion")
LOCK -^Lock.RefData("ref")#"S"
} Catch ex {
$$$TRACE("Release shared lock on error")
LOCK -^Lock.RefData("ref")#"S"
THROW ex // re-throw the exception after releasing lock
}
} Catch ex {
Set sc = ex.AsStatus()
}
Set pResponse = ##class(Ens.Response).%New()
$$$TRACE("Done.")
Quit sc
}
}生产
为了展示这一切是如何协同工作的,我还添加了一个带有业务规则的路由器,该规则调用一个自定义函数,以及一个每秒调用路由器的业务服务。下面是生产事件日志的示例:
14:26:39.780 时,更新器获得共享锁,并立即将其升级为独占锁。在 14:26:40.360 时,一个自定义函数请求共享锁,但直到 14:26:44.785 时才获得共享锁,紧接着更新器业务操作释放了独占锁。
结论
锁为管理多进程系统中的并发性提供了底层工具。