Lock Command, continued
The regular form of Lock is an exclusive lock. As you've seen, exclusive locks prevent all other locks. Caché also supports shared locks, using the syntax lock +^PersonD(id)#"s" . Multiple processes may take out the same shared lock. But a shared lock prevents an exclusive lock, and an exclusive lock prevents a shared lock.
Since all Lock does is check, add, and delete entries in the Lock Table, when using Lock, you don't even have to specify the name of an existing global. For example, you could decide to lock +^Person(id) (instead of lock +^PersonD(id)) whenever you're editing ^PersonD, even though ^Person doesn't exist. It would work just as well, because ^Person would be added to the Lock Table, and all the code that edits the ^PersonD array will try to lock ^Person.
You don't need to also lock ^PersonI when editing persons, even though this global contains person data. This is because you're not actually “locking” the arrays; you're simply adding an entry to the Lock Table, which has the effect of locking the code that allows editing of the globals. In an application, all the code that updates ^PersonD would use this same convention. Since all the code that updates ^PersonD always updates ^PersonI at the same time, lock +^PersonD(id) is enough.
When ^PersonD(1) is in the Lock Table, another process can't lock ^PersonD(1), yet it could lock ^PersonD(2). In other words, another process can lock siblings of entries in the Lock Table. However, another process can't lock ancestors of entries in the lock Table (^PersonD in this case). This is because you wouldn't want to allow access to a higher level of the array if one of its lower nodes is locked. Similarly, another process can't lock descendants of entries in the Lock Table; ^PersonD(1,2) for example. You wouldn't want to allow access to the descendants of a node if the node itself is locked.