第十六章 Caché 命令大全 LOCK 命令

第十六章 Caché 命令大全 LOCK 命令

使进程能够应用和释放锁定以控制对数据资源的访问。

重点

  1. 理解增量锁,递减锁,共享锁,升级锁,立即解锁,延迟解锁,排他锁,独占锁。
  2. 无参数LOCK 释放当前进程所有锁.
  3. 无符号锁,会释放之前的锁,在加上当前的锁。
  4. - 要删除共享锁和/或升级锁,必须指定相应的#locktype
  5. a(1) 这种锁不是跨进程的,只适用于该进程。

大纲

LOCK:pc
L:pc

LOCK:pc +lockname#locktype:timeout,...
L:pc +lockname#locktype:timeout,...

LOCK:pc +(lockname#locktype,...):timeout,...
L:pc +(lockname#locktype,...):timeout,...

参数

  • pc 可选,后置表达式
  • + – 可选-应用或删除锁定的锁定操作指示符(+字符、-字符或无字符)。+(加号)应用指定的锁,而不解锁任何先前的锁。这可用于应用增量锁。-(减号)解锁(或递减)锁。如果省略锁定操作指示符(无字符),则Caché将解锁所有先前的锁定并应用指定的锁定。
  • lockname 与要锁定或解锁的资源关联的锁名。必须是有效的标识符,并遵循与局部变量或全局变量相同的命名约定。
  • #locktype 可选-指定要锁定或解锁的锁定类型的字母代码,用引号指定。可选值为“S”(共享锁)、“E”(升级锁)、“I”(立即解锁)和“D”(延迟解锁)。指定时,前面的#符号是必需的。指定时,前面的#符号是必需的。例如,#“S”。可以指定多个字母代码。例如,#“SEI”“S”“E”同时指定用于锁定和解锁操作;“I”“D”仅指定用于解锁操作。如果省略,锁类型默认为排他锁(非S),它不会升级(非E),并且始终将释放解锁的锁推迟到当前事务结束(非I/非D)。
  • :timeout 可选-尝试的锁定操作超时之前的等待时间。可以使用或不使用可选的#locktype指定。指定时,前面的:符号是必需的。 例如,锁定^a(1):10或锁定^a(1)#“E”:10将超时指定为整数秒。值0表示进行一次尝试,然后超时。小数秒被截断为整数部分。如果省略,Caché将无限期等待。

描述

LOCK命令有两种基本形式:

  • 有参数
  • 无参数

不带参数的锁定

无参数锁释放(解锁)进程当前持有的所有锁。这包括本地和全局独占和共享锁定。它还包括所有累积的增量锁定。 例如,如果给定锁名称上有三个增量锁,则Caché将释放所有三个锁,并从锁表中删除锁名称条目。

如果在事务期间发出无参数锁,则Caché会将进程当前持有的所有锁置于Delock状态,直到事务结束。当事务结束时,Caché释放锁,并从锁表中删除相应的锁名称条目。

下面的示例在事务期间应用各种锁,然后发出一个无参数锁以释放所有这些锁。在事务结束之前,锁一直处于解除锁定状态。HANG命令可以有时间检查锁表中的锁的ModeCount:

/// d ##class(PHA.TEST.Command).TestLOCK()
ClassMethod TestLOCK()
{
	TSTART
	LOCK +^a(1)      // ModeCount: Exclusive
	HANG 6
	LOCK +^a(1)      // ModeCount: Exclusive/2
	HANG 6
	LOCK +^a(1)#"E"  // ModeCount: Exclusive/2+1e
	HANG 6
	LOCK +^a(1)#"E"  // ModeCount: Exclusive/2+2e
	HANG 6
	LOCK +^a(1)#"S"  // ModeCount: Exclusive/2+2e,Shared
	HANG 6
	LOCK +^a(1)#"S"  // ModeCount: Exclusive/2+2e,Shared/2
	HANG 6
	LOCK             // ModeCount: Exclusive/2+2e->Delock,Shared/2->Delock
	HANG 10
	TCOMMIT          // ModeCount: locks removed from table
}

独占锁 2次,升级锁 2次,共享锁 2次, 最后全部解锁。

无参数锁释放进程持有的所有锁,而不应用任何锁。进程的完成还会释放该进程持有的所有锁。

使用参数锁定

可以通过以下两种方式之一使用单个LOCK命令指定多个锁定:

  • 不带括号:通过将多个不带括号的锁参数指定为逗号分隔的列表,可以指定多个独立的锁操作,每个操作都可以有自己的超时。(这在功能上等同于为每个锁定参数指定单独的锁定命令。)

锁定操作严格按照从左到右的顺序执行。例如:

扫描二维码关注公众号,回复: 11408877 查看本文章
/// d ##class(PHA.TEST.Command).TestLOCK2()
ClassMethod TestLOCK2()
{
	LOCK var1(1):10,+var2(1):15
}

不带括号的多个锁参数每个都可以有自己的锁操作指示符和自己的超时参数。但是,如果使用多个锁定参数,请注意,没有加号锁定操作指示符的锁定操作会解锁所有以前的锁定,包括由同一锁定命令的较早部分应用的锁定。

例如,命令LOCK^b(1,1)^c(1,2,3)^d(1)将被解析为三个单独的LOCK命令:第一个命令释放进程以前持有的锁(如果有)并锁定^b(1,1),第二个命令立即释放^b(1,1)LOCKING^c(1,2,3),第三个命令立即释放^c(1,2,3)LOCKING^d(1)。因此,只有^d(1)会被锁定。

DHC-APP>LOCK ^b(1,1), ^c(1,2,3), ^d(1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JX1wHHl6-1594861199190)(A129FDDE4B9A493D8C92F5054DB4837A)]

  • 使用圆括号:通过将逗号分隔的锁名列表括在圆括号中,可以将这些锁定操作作为单个原子操作对多个锁执行。

例如:

DHC-APP> LOCK +(var1(1),var2(1)):10

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvknyVOA-1594861199193)(21077DE5782D44458751022024795865)]

括号括起来的列表中的所有锁操作都由单个锁操作指示符和单个超时参数管理;要么应用所有锁,要么不应用任何锁。没有加号锁操作指示符的括号括起来的列表解锁所有先前的锁,然后锁定所有列出的锁名。

DHC-APP> LOCK (var3(1),var4(1)):10

在这里插入图片描述

参数

pc

可使命令具有条件的可选后置条件表达式。如果后置条件表达式为TRUE(计算结果为非零数值),则Caché执行LOCK命令。如果后置条件表达式为假(计算结果为零),则Caché不执行命令。可以在无参数LOCK命令或带参数的LOCK命令上指定后置条件表达式。

LOCK操作指示器(操作符)

锁定操作指示器用于应用(锁定)或移除(解锁)锁定。它可以是下列值之一:

  • 无符号 解锁属于当前进程的所有先前锁,并尝试应用指定的锁。例如,lock^a(1)执行以下原子操作:它释放进程以前持有的所有锁(无论是本地的还是全局的、独占的还是共享的、升级的还是非升级的),并尝试锁定^a(1)。这可能导致以下两种结果之一:(1)所有在先的锁被解锁,^a(1)被锁定;(2)所有在先的锁被解锁,^a(1)处于锁定等待状态,等待释放由另一进程持有的冲突锁。

  • + 符号 尝试在不执行任何解锁的情况下应用指定的锁。这允许向当前进程持有的锁添加锁。此选项的一个用途是执行锁名称的增量锁定。

  • - 符号 解锁指定的锁。如果锁名称的锁计数为1,则解锁将从锁表中删除该锁。如果锁定名称的锁定计数大于1,则解锁将移除其中一个增量锁定(递减锁定计数)。默认情况下,这会解锁独占的非升级锁。要删除共享锁和/或升级锁,必须指定相应的#locktype

如果LOCK命令包含多个逗号分隔的锁参数,则每个锁参数可以有自己的锁操作指示符。Caché将其解析为多个独立的锁定命令。

lockname

锁名是数据资源的锁的名称;它不是数据资源本身。也就是说,程序可以无冲突地指定一个名为^a(1)的锁和一个名为^a(1)的变量。锁和数据资源之间的关系是编程约定;按照约定,进程在修改相应的数据资源之前必须获取锁。

锁名称区分大小写。锁名称遵循与相应的局部变量和全局变量相同的命名约定。锁名称可以下标或取消下标。锁下标与变量下标具有相同的命名约定、最大长度和级别数。在Caché中,以下都是有效且唯一的锁名:a, a(1), A(1), ^a, ^a(1,2), ^A(1,1,1)

注意:出于性能原因,建议尽可能使用下标指定锁名。例如,文档示例中使用的是^a(1)而不是^a

锁名称可以是本地的,也可以是全局的。诸如A(1)之类的锁名是本地锁名。它只适用于该进程,但确实适用于所有名称空间。以脱字符(^)开头的锁名是全局锁名;此锁的映射遵循与相应全局相同的映射,因此可以跨进程应用,从而控制它们对相同资源的访问。

注意:私有进程全局名不能用作锁名。尝试使用进程专用全局名称作为锁名不执行任何操作,并且完成时不会发出错误。

锁名称可以表示局部变量或全局变量(带下标或不带下标)。它可以是隐式全局引用,也可以是对另一台计算机上的全局的扩展引用。

锁名称对应的数据资源不需要存在。例如,无论是否存在同名的全局变量,都可以锁定锁名称^a(1,2,3)。因为锁和数据资源之间的关系是公认的约定,所以可以使用锁来保护具有完全不同名称的数据资源。

locktype

  • 指定要应用或删除的锁类型的字母代码。
  • locktype是可选参数;如果省略locktype,则锁类型默认为独占的非升级锁。
  • 如果省略locktype,则必须省略井号(#)前缀。
  • 如果指定locktype,则LOCK TYPE的语法是必需的井号(#),后跟一个或多个LOCK类型字母代码的引号。
    锁类型字母代码可以按任何顺序指定,并且不区分大小写。

以下是锁型字母代码:

  • S: 分享琐

允许多个进程同时持有同一资源上的无冲突锁。例如,两个(或多个)进程可以同时持有同一资源上的共享锁,但排他锁将资源限制为一个进程。现有的共享锁可防止所有其他进程应用排他锁,而现有的排他锁可防止所有其他进程对该资源应用共享锁。但是,进程可以首先在资源上应用共享锁,然后同一进程可以在资源上应用独占锁,从而将锁从共享升级为独占。共享锁计数和独占锁计数是独立的。因此,要释放这样的资源,需要同时释放排他锁和共享锁。所有未指定为共享(“S”)的锁定和解锁操作默认为独占。

  • E: 升级锁

允许应用大量并发锁,而不会使锁表溢出。默认情况下,锁定是非升级的。应用锁时,可以使用锁类型“E”将该锁指定为升级。默认情况下,锁定是非升级的。应用锁时,可以使用锁类型“E”将该锁指定为升级。可以将排他锁和共享(“S”)锁都指定为升级。

通常,在同一下标级别应用大量并发锁时,会使用升级锁。例如:LOCK +^mylock(1,1)#"E",+^mylock(1,2)#"E",+^mylock(1,3)#"E"

同一锁可以作为非升级锁和升级锁同时应用。例如: ^mylock(1,1)^mylock(1,1)#"E".Caché在锁表中分别对锁类型为“E”的锁进行计数。

当下标级别的“E”锁的数量达到阈值数量时,为该下标级别请求的下一个“E”锁自动尝试锁定父节点(下一个更高的下标级别)。如果不能,则不会发生升级。如果成功锁定父节点,则建立一个父节点锁,其锁计数对应于较低下标级别的锁数加1。较低下标级别的锁定将被释放。对较低下标级别的后续“E”锁请求进一步递增该父节点锁的锁计数。必须解锁所有已应用的“E”锁,以便将父节点锁计数减少到0并降低到更低的下标级别。默认锁阈值是1000个锁;请求第100个锁时会升级锁。

请注意,升级锁定后,锁定操作仅保留应用的锁数,而不保留锁定的特定资源。因此,未能解锁锁定的相同资源可能会导致“E”锁计数不同步。

在下面的示例中,当程序应用锁阈值+1“E”锁时,就会发生锁升级。此示例显示,锁定升级既在下一个更高的下标级别上应用锁定,又在较低的下标级别上释放锁定:

/// d ##class(PHA.TEST.Command).TestLOCKE()
ClassMethod TestLOCKE()
{

	TSTART
	SET thold = $SYSTEM.SQL.GetLockThreshold()
	WRITE "锁定升级阈值为 ",thold,!
	SET almost = thold - 5
	FOR i = 1 : 1 : thold + 5 { 
		LOCK +dummy(1, i)#"E"
		IF i > almost {
			IF ^$LOCK("dummy(1,"_i_")","OWNER") '= "" {
				WRITE "较低级别的锁定应用于 ",i,"th 锁 ",! 
			}
			ELSEIF ^$LOCK("dummy(1)","OWNER") '= ""   {
				WRITE "锁定升级",!
				WRITE "更高级别的锁定应用于 ",i,"th 锁 ",!
				QUIT 
			}
			ELSE {
				WRITE "未应用任何锁定",! 
			}
		}
	}
	TCOMMIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestLOCKE()
锁定升级阈值为 10000
较低级别的锁定应用于 9996th 锁
较低级别的锁定应用于 9997th 锁
较低级别的锁定应用于 9998th 锁
较低级别的锁定应用于 9999th 锁
较低级别的锁定应用于 10000th 锁
锁定升级
更高级别的锁定应用于 10001th 锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EMChcV85-1594861199197)(7693AE6CE20D4DB48F75A0B3BC6D0CC5)]

请注意,只有“E”锁计入锁升级。

下面的示例在同一变量上同时应用默认(非“E”)锁和“E”锁。仅当变量上的“E”锁总数达到锁阈值时,才会发生锁升级:

/// d ##class(PHA.TEST.Command).TestNonEscalatingLocks()
ClassMethod TestNonEscalatingLocks()
{
	TSTART
	SET thold = $SYSTEM.SQL.GetLockThreshold()
	WRITE "锁定升级临界值为 ",thold,!
	SET noE = 17
	WRITE "设置 ",noE," 非升级锁",!
	FOR i = 1 : 1 : thold + noE { 
		IF i < noE {
			LOCK +a(6, i)
		}
		ELSE {
			LOCK +a(6, i)#"E"
		}
		IF ^$LOCK("a(6)","OWNER") '= "" {
			WRITE "锁升级 on lock a(6,",i,")",!
			QUIT 
		}
	}
	TCOMMIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestNonEscalatingLocks()
锁定升级临界值为 10000
设置 17 非升级锁
锁升级 on lock a(6,10017)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovi2TzO8-1594861199198)(6AA9D54D61FD403AB247E3DCE2211AC3)]

解锁“E”锁与上述相反。当锁升级时,在子级解锁会递减父节点锁的锁计数,直到它达到零(并且已解锁);这些解锁会递减计数,它们与特定的锁不匹配。当父节点锁计数达到0时,父节点锁被移除,“E”锁降级到更低的下标级别。较低下标级别的任何后续锁都会在该级别创建特定的锁。

“E”锁型可以与任何其他锁型组合。例如,“SE”、“ED”、“EI”、“SED”、“SEI”当与“I”锁类型结合使用时,它允许在调用时立即解锁“E”锁,而不是在当前事务结束时解锁。 这种“EI”锁类型可以最大限度地减少升级锁定的情况。

通常,“E”锁自动应用于事务中的SQL INSERT、UPDATE和DELETE操作。但是,支持“E”锁定的SQL数据定义结构有特定的限制。

  • I:立即解锁

立即释放锁,而不是等到事务结束:

  • 解锁非增量(锁计数为1)锁时指定“I”会立即释放锁。默认情况下,解锁不会立即释放非增量锁。相反,当解锁非增量锁时,Caché会将该锁保持在解锁状态,直到事务结束。指定“I”将覆盖此默认行为。
  • 在解锁增量锁(锁计数>1)时指定“i”会立即释放增量锁,从而将锁计数递减1。这与增量锁的默认解锁行为相同。

在事务期间执行解锁时使用“I”锁类型。无论锁是在事务内还是在事务外应用,都会对Caché 解锁行为产生相同的影响。如果解锁发生在事务之外,则“I”锁类型不执行任何操作。在事务之外,解锁总是立即释放指定的锁。

“I”只能为解锁操作指定;不能为锁定操作指定。可以为共享锁(#“SI”)或排他锁(#“I”)的解锁指定“I”。锁类型“I”“D”是互斥的。“IE”可用于立即解锁升级锁。

此立即解锁如下例所示:

/// d ##class(PHA.TEST.Command).TestImmediateUnlock()
ClassMethod TestImmediateUnlock()
{
	TSTART
	LOCK +^a(1)      // 应用 lock ^a(1)
	LOCK -^a(1)      // 移除 (unlock) ^a(1)
	                 // 不带锁类型的解锁将非增量锁的解锁推迟到事务结束。
	              
	WRITE "事务内的默认解锁。",!,"查看锁表",!
	HANG 10          // 此挂起查看当前的锁定表
	LOCK +^a(1)      // 重复应用 lock ^a(1)
	LOCK -^a(1)#"I"  // 立即移除 (unlock) lock ^a(1) 
	                 // 这将立即从锁表中删除^a(1),而无需等待事务结束
	WRITE "在事务内立即解锁.",!,"查看锁表",!
	HANG 10          // 此挂起查看当前的锁定表,同时仍在事务中
	TCOMMIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestImmediateUnlock()
事务内的默认解锁。
查看锁表
在事务内立即解锁.
查看锁表
  • D:延迟解锁

控制事务期间何时释放未锁定的锁。解锁状态被推迟到该锁的上一次解锁状态。因此,在解锁锁时指定locktype“D”可能会导致立即解锁或将锁置于解锁状态直到事务结束,具体取决于该事务期间的锁的历史记录。已多次锁定/解锁的锁的行为与在当前事务期间仅锁定一次的锁的行为不同。

“D”解锁仅对释放锁定(锁定计数1)的解锁有意义,而对减少锁定(锁定计数>1)的解锁没有意义。减少锁的解锁总是立即释放。

只能为解锁操作指定“D”。可以为共享锁(#“SD”)或排他锁(#“D”)指定“D”。可以为升级(“E”)锁指定“D”,但当然,还必须将解锁指定为升级(“ED”)。锁类型“D”“I”是互斥的。

事务内“D”解锁的这种用法如以下示例所示。HANG命令有时间在锁表中检查锁的ModeCount。

如果该锁在当前事务期间只应用了一次,则“D”解锁会立即释放该锁。这与“I”的行为是一样的。下面的示例显示了这一点:

/// d ##class(PHA.TEST.Command).TestDeferredUnlock()
ClassMethod TestDeferredUnlock()
{
	TSTART
	LOCK +^a(1)      // 锁表模式计数:独占
	LOCK -^a(1)#"D"  // 锁表模式计数:NULL(立即解锁)
	HANG 10
	TCOMMIT
}

如果在当前事务期间多次应用锁定,则“D”解锁将恢复到先前的解锁状态。

  • 如果上一次解锁是标准解锁,则“D”解锁会将解锁行为恢复为前一次解锁的行为-将解锁推迟到事务结束。

以下示例显示了这一点:

  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)      // Lock Table ModeCount: Exclusive
     WRITE "1st unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive->Delock
     WRITE "2nd unlock",! HANG 5
  TCOMMIT
  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)      // Lock Table ModeCount: Exclusive->Delock
     WRITE "1st unlock",! HANG 5
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive->Delock
     WRITE "2nd unlock",! HANG 5
  TCOMMIT

  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"I"  // Lock Table ModeCount: Exclusive/2
     WRITE "1st unlock",! HANG 5
  LOCK -^a(1)      // Lock Table ModeCount: Exclusive
     WRITE "2nd unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive->Delock
     WRITE "3rd unlock",! HANG 5
  TCOMMIT
  • 如果上一次解锁是“I”解锁,则“D”解锁会将解锁行为恢复为前一次解锁的行为-立即解锁。

以下示例显示了这一点:

  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"I"  // Lock Table ModeCount: null (immediate unlock)
     WRITE "1st unlock",! HANG 5
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"D"  // Lock Table ModeCount: null (immediate unlock)
     WRITE "2nd unlock",! HANG 5
  TCOMMIT
  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"I"  // Lock Table ModeCount: Exclusive
     WRITE "1st unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: null (immediate unlock)
     WRITE "2nd unlock",! HANG 5
  TCOMMIT

  • 如果上一次解锁是“D”解锁,则“D”解锁遵循上一次非“D”锁的行为:
  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive
     WRITE "1st unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: null (immediate unlock)
     WRITE "2nd unlock",! HANG 5
  TCOMMIT
  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)      // Lock Table ModeCount: Exclusive/2
     WRITE "1st unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive
     WRITE "2nd unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive->Delock
     WRITE "3rd unlock",! HANG 5
  TCOMMIT
  TSTART
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK +^a(1)      // Lock Table ModeCount: Exclusive
  LOCK -^a(1)#"I"  // Lock Table ModeCount: Exclusive/2
     WRITE "1st unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: Exclusive
     WRITE "2nd unlock",! HANG 5
  LOCK -^a(1)#"D"  // Lock Table ModeCount: null (immediate unlock)
     WRITE "3rd unlock",! HANG 5
  TCOMMIT

timeout

在超时之前等待锁定请求成功的秒数。超时是一个可选参数。如果省略,LOCK命令将无限期等待资源可锁定;如果无法应用锁定,则进程将挂起。 超时的语法是强制冒号(),后跟整数值或计算结果为整数值的表达式。零值允许在超时之前进行一次锁定尝试。负数等于零。

通常,如果另一个进程具有阻止此锁请求获取(持有)指定锁的冲突锁,则锁将等待。锁定请求将一直等待,直到释放解决冲突的锁定,或者锁定请求超时。终止该进程还会结束(删除)挂起的锁定请求。锁冲突可能由多种情况引起,而不仅仅是一个进程请求由另一个进程持有相同的锁。

如果使用超时并且锁定成功,则Caché会将$test特殊变量设置为1(True)。如果无法在超时期限内应用锁,则Caché会将$test设置为0(False)。
发出不带超时的锁定请求不会影响$test的当前值。请注意,$test也可以由用户设置,也可以由JOB, OPENREAD超时设置。

下面的示例对锁名称^abc(1,1)应用锁,并解锁进程持有的所有先前锁:

    LOCK ^abc(1,1)

此命令请求独占锁定:任何其他进程都不能同时持有对此资源的锁定。如果另一个进程已经持有此资源的锁(独占或共享),则此示例必须等待该锁被释放。它可以无限期地等待,挂起这个过程。要避免这种情况,强烈建议指定超时值:

    LOCK ^abc(1,1):10

如果一个锁在逗号分隔的列表中指定了多个锁名参数,则每个锁名资源可能有其自己的超时(不带括号的语法),或者所有指定的锁名资源可能共享单个超时(带括号的语法)。

  • 不带括号:每个锁名参数都可以有自己的超时。Caché将其解析为多个独立的锁命令,因此一个锁参数的超时不会影响其他锁参数。锁参数严格按照从左到右的顺序进行解析,每个锁请求要么完成,要么在尝试下一个锁请求之前超时。

  • 带括号:所有锁名参数共享一个超时。锁必须在超时期限内成功应用所有锁定(或解锁)。如果在所有锁定成功之前超时,则不执行LOCK命令中指定的任何锁定操作,并且控制返回到该进程。

Caché严格按照从左到右的顺序执行多个操作。因此,在不带括号的锁语法中,$test值表示多个锁名锁请求中最后一个(最右边)的结果。

在下面的示例中,当前进程无法锁定^a(1),因为它被另一个进程以独占方式锁定。这些示例使用超时值0,这意味着它们尝试一次应用指定的锁。

    LOCK +^x(1):0,+^a(1):0,+^z(1):0

第二个示例锁定^x(1)^z(1)。它设置$test=0是因为^a(1)超时。^z(1)未指定超时,因此对$TEST没有影响:

    LOCK +^x(1):0,+^a(1):0,+^z(1):0

第三个示例不应用锁,因为括号中的锁列表是原子操作(全有或全无)。它设置$test=0是因为^a(1)超时::

    LOCK +(^x(1),^a(1),^z(1)):0

使用锁表在系统范围内查看和删除锁:

Caché维护一个系统范围的锁表,该表记录有效的所有锁以及应用这些锁的进程。系统管理员可以显示锁表中的现有锁或使用管理门户界面或^LOCKTAB实用程序删除选定的锁.还可以使用%SYS.LockQuery类读取锁表信息。在%SYS命名空间中,可以使用SYS.Lock类来管理锁表。

可以使用管理门户在系统范围内查看挂起的锁定和挂起的锁定请求。转到管理门户,选择系统操作,选择锁定,然后选择查看锁定([主页]>[查看锁定])。

可以使用管理门户删除(删除)系统上当前持有的锁定。转到管理门户,选择系统操作,选择锁定,然后选择管理锁定([主页]>[管理锁定])。对于所需的进程(所有者),单击“Remove”(删除)或“Remove all Lock for Process”(删除进程的所有锁定)。

删除锁将释放该锁的所有形式:锁的所有增量级别、锁的所有独占版本、独占升级版本和共享版本。删除锁会立即导致应用该锁队列中等待的下一个锁。

还可以使用SYS.Lock.DeleteOneLock()SYS.Lock.DeleteAllLock()方法删除锁定。

删除定需要写入权限。锁删除记录在审核数据库中(如果启用),而不记录在cconsole.log中。

增量锁和解锁

增量锁允许多次应用相同的锁定:递增锁。增量锁的锁计数大于1。进程随后可以递增和递减此锁计数。当锁计数递减到0时,锁被释放。在锁计数减到0之前,没有其他进程可以获取锁。锁表为排他锁和共享锁以及每种类型的升级锁和非升级锁维护单独的锁计数。最大增量锁计数为32,766。尝试超过此最大锁定计数会导致<Max LOCKS>错误。

可以按如下方式递增锁:

  • 加号:使用加号锁定操作指示符在同一锁名称上指定多个锁定操作。例如: LOCK +^a(1) LOCK +^a(1) LOCK +^a(1) 或 LOCK +^a(1),+^a(1),+^a(1) 或 LOCK +(^a(1),^a(1),^a(1)). 所有这些都会导致锁表ModeCount为Exclusive/3。建议使用加号递增锁。

  • 无符号:通过指定执行多个锁的原子操作,可以在不使用加号锁操作指示符的情况下递增锁。例如,LOCK (^a(1),^a(1),^a(1))解锁所有先前的锁,并递增锁定^a(1)三次。这也会导致锁表ModeCount为EXCLUSIVE/3。虽然此语法有效,但不建议这样做。

在不在事务中时解锁递增的锁只会递减锁计数。在事务中解锁增量锁具有以下默认行为:

  • 递减解锁:每次递减解锁都会立即释放递增解锁,直到锁定计数为1。默认情况下,最终解锁将锁置于解锁状态,将锁的释放推迟到事务结束。当使用减号锁定操作指示符解锁时,无论操作是否为原子操作,都会出现这种情况。例如: LOCK -^a(1) LOCK -^a(1) LOCK -^a(1) 或 LOCK -^a(1),-^a(1),-^a(1) 或 LOCK -(^a(1),^a(1),^a(1)). 所有这些都以锁表ModeCount独占/3开始,以Exclusive->Delock.结束。
  • 解锁先前资源:解锁所有先前资源的操作会立即将递增的锁置于解锁状态,直到事务结束。例如,lock x(3)(没有锁操作指示符的锁)或无参数锁将具有以下效果:增量锁将以锁表ModeCount Exclusive/3开始,以 Exclusive/3->Delock结束。

请注意,对于与独占锁、共享锁、独占升级锁和共享升级锁相同的锁,维护单独的锁计数。在下面的示例中,第一次解锁将LOCK^a(1)的四个单独的锁计数递减1。第二次解锁必须指定所有四个^a(1)锁才能删除它们。HANG命令使有时间在锁表中检查锁的ModeCount。

/// d ##class(PHA.TEST.Command).TestIncreaseLock()
ClassMethod TestIncreaseLock()
{
	LOCK +(^a(1),^a(1)#"E",^a(1)#"S",^a(1)#"SE")
	LOCK +(^a(1),^a(1)#"E",^a(1)#"S",^a(1)#"SE")
	HANG 10
	LOCK -(^a(1),^a(1)#"E",^a(1)#"S",^a(1)#"SE")
	HANG 10
	LOCK -(^a(1),^a(1)#"E",^a(1)#"S",^a(1)#"SE")
}

如果尝试解锁未应用当前锁定的锁名,则不会执行任何操作,也不会返回任何错误。

自动解锁

当进程终止时,Caché执行一个隐式无参数锁,以清除该进程应用的所有锁。它同时删除挂起的锁定和锁定等待请求.

对全局变量的锁定

锁通常与全局变量一起使用,以同步可能同时访问这些变量的多个进程的活动。全局变量与局部变量的不同之处在于,它们驻留在磁盘上,并且对所有进程都可用。因此,存在两个进程同时写入同一全局的可能性。实际上,Caché先处理一个更新,然后再处理另一个更新,因此一个更新会覆盖另一个更新,实际上会丢弃另一个更新。

全局锁名称以脱字符(^)开头。

为了说明使用全局变量进行锁定,请考虑两个数据录入办事员同时运行相同的学生招生应用程序以添加新注册学生的记录的情况。这些记录存储在名为^Student的全局数组中。为了确保每个学生的记录是唯一的,应用程序为添加的每个学生递增全局变量 ^index 。该应用程序包括LOCK命令,以确保将每个学生记录添加到数组中的唯一位置,并且一个学生记录不会覆盖另一个学生记录。

应用程序中的相关代码如下所示。在本例中,锁控制的不是全局数组^Student,而是全局变量^index^index是由两个进程共享的临时全局索引。在进程可以将记录写入数组之前,它必须锁定^INDEX并更新其当前值(SET ^index=^index+1)。如果另一个进程已经在这段代码中,^index将被锁定,并且该进程将不得不等待,直到另一个进程释放锁(使用无参数锁定命令)。)

/// d ##class(PHA.TEST.Command).TestLockStudent()
ClassMethod TestLockStudent()
{
	READ !,"Last name: ",!,lname QUIT:lname=""  SET lname=lname_"," 
	READ !,"First name: ",!,fname QUIT:fname=""  SET fname=fname_"," 
	READ !,"Middle initial: ",!,minit QUIT:minit=""  SET minit=minit_":" 
	READ !,"Student ID Number: ",!,sid QUIT:sid=""
	SET rec = lname_fname_minit_sid
	
	LOCK ^index
	SET ^index = +^index + 1
	SET ^student(^index)=rec
	LOCK
}

下面的示例重新转换前面的示例,以便在要添加到^Student数组的节点上使用锁定。只显示代码的受影响部分。在本例中,^index变量在添加新学生记录后更新。下一个添加记录的过程将使用更新后的索引值写入正确的数组节点。

    LOCK ^student(^index)
    SET ^student(^index) = rec
    SET ^index = ^index + 1
    LOCK  /* 释放所有锁 */

请注意,数组节点的锁定位置是映射顶级全局的位置。Caché在确定锁定位置时忽略下标。因此,无论^Student(Name)的数据存储在哪里,^Student(Name)都映射到^Student的名称空间。

网络中的锁

在联网系统中,一个或多个服务器可能负责解析全局变量上的锁定。

可以对任意数量的服务器使用lock命令,最多255个。当调用^RESJOB实用工具删除客户端JOB时,远程服务器系统上的客户端JOB持有的远程锁定将被释放。

局部变量锁

在Caché、Open M[DTM]和用于OpenVMS的DSM上,本地(无插入符号)变量上的锁定行为是相同的。Config.Miscellous类提供ZaMode属性,可以使用该属性在系统范围内设置锁定模式,以便与旧版DSM-11 ZALLOCATE(ZA)和ZDEALLOCATE(ZD)LOCKING命令兼容。

在Open M[DTM]和DSM for OpenVMS中,无论第一个字符是什么,管理器数据集中的本地锁总是被取出。如果移植到Caché的应用程序使用不是以%字符开头的本地锁,如果应用程序在不同的数据集中使用相同的本地非%锁名称,则该应用程序可能会遇到死锁情况。锁现在会发生冲突,因为它们解析为同一项。如果这是分层锁定系统的一部分,则可能会发生致命的bug,从而导致应用程序挂起。

当前行为是:

  • 在本地计算机上管理器的数据集中取出在特定命名空间上下文中获取的本地(无插入符号)锁,因为默认命名空间是显式命名空间,或者是通过对命名空间的显式引用获得的。无论全局变量的默认映射是本地数据集还是远程数据集,都会发生这种情况。
  • 在隐含名称空间的上下文中或通过显式引用本地计算机上的隐含名称空间而获得的本地(无插入符号)锁,是使用本地计算机的管理器数据集取出的。隐含的名称空间是目录路径或OpenVMS文件规范,前面有两个脱字符:\“^^dir\”

猜你喜欢

转载自blog.csdn.net/yaoxin521123/article/details/107375315
今日推荐