作者

Sales Engineer at Intersystems
文章 Jeff Liu · 四月 21 4m read

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 时才获得共享锁,紧接着更新器业务操作释放了独占锁。

结论

锁为管理多进程系统中的并发性提供了底层工具。

链接