Title:

getLock() -obtain a mutually exclusive lock represented by the threadLock object supplied. If a condition is specified, this function does not return until the condition is true and provides a mechanism similar to condition variables in POSIX threads. This allows synchronization on the values of shared variables rather than on access to shared variables provided by threadLock objects.

Usage:

getLock(..., condition, safe=F)

Arguments:

...:
a collection of threadLock objects for which to acquire the lock. These can threadLock objects, and also strings in which case the object is found in the internal table using the name supplied when the object was created or returned in the threadLock object. Additionally, we might let these be integers referencing a threadLock object in the search path by index. This would work in general whereever a threadLock object was expected and an integer or string was supplied. If condition is supplied, the threadLock objects passed here should be the databases containing the variables being tested in condition. It is advisable to keep objects involved in a condition in the same threadLock for simplicity. I believe multiple threadLock objects will work, but will probably be inefficient and may be removed.
condition:
if this argument is supplied it must be either an expression (that evaluates to T) or a string (or character vector) which is the name of a variable. If the argument is an expression, it is evaluated within the lock (when acquired) and while the condition evaluates to F the lock is released and the thread waits to be notified by other threads of a change in the variables associated with this expression.
What (will) actually happen initially more than likely is that this thread is awoken each time any variable in any of the threadLock objects is modified and the condition retested. This might be inefficient.
When the condition evaluates to T, the call returns and the lock is held.

If the argument is a string, it is taken to be the name of a variable in this threadLock object. The thread requesting the lock is notified of any assignments to this variable, whether it exists or is being created. This is used by a thread to be arranged to be notified of any changes to a particular variable.

If the argument is a vector of character strings, the above applies if any of the variables is modified.

We should think about signalling removals, but this is an extreme case.

The purpose of this is to test the values of variables which are shared across threads. The test must be protected to ensure no thread modifies the variables during the evaluation of the expression and also that the variables have consistent values.

This is different from Pthreads in which the thread must explicitly call pthread_mutex_lock() and pthread_mutex_unlock() and have a call to pthread_cond_wait() inside the lock. So code here is asymmetric.

safe:
if T, the evaluation manager is queried to determine if the calling thread already holds the lock. If so, the function returns. This is for novice users or in the case of extreme bugs. If the calling thread already holds the lock, it is highly probable that the collection of threads has been misdesigned. This argument merely prevents deadlock.

Side Effect:

The evaluation of this thread will be stopped until the lock is acquired. If another thread holds this lock when this call is evaluated, the this call blocks and competes with other threads for the lock when the holder releases it. No order is guaranteed for the queue of threads waiting for the lock. When the call returns, the lock will be held and must be released by this thread.

NOTE:

Potential Deadlock.
If the calling thread holds the lock, deadlock will arise. This can be guarded against using safe, but will most likely incur a significant performance penalty.
Access Deadlock
The usual scenario is that a thread calls getLock and then tries to access an object in that threadLock object. However, if the threadLock object internally forces mutual exclusion when getting or assigning objects to itself, there is potential deadlock. So threadLock objects must check to see who is holding its lock and continue if it is the calling thread during an assign or get.
Multiple Variables in a Condition across Multiple threadLock objects
Consider the following arrangment.
     a = threadLock("a", n = 1)
     b = threadLock("b", i = 1)
      i == 0 && n == 1
     
Even if these objects are in the same threadLock object, we might have problems. Imagine the scenario where i is retrieved and a copy returned. Then a second thread modifies i. Then n is retrieved and the condition evaluated. At this stage, we don't have consistency and the condition might/will be erroneously evaluated. Instead, we must lock access to i and n. So we lock a and b. Now this may cause grief. So we would have
      getLock(a); getLock(b)
       i == 0 && n == 1
      yieldLock(b); yieldLock(a)
     
(Note the order in which we acquire and release the locks - a good thing in general). Consider the case where one (or more) of these conditions is false, say i == 1. Then the condition evaluates to F.

The best way to arrange these computations is to have n and i in a single threadLock object together. Of course, if we have numerous conditions to test, each comparing partially insersecting sets of variables (e.g. {a,b}, {b,c}, {c,d}, {a,b}), then this threadLock object becomes quite large and all assignments and 'get's to it cause coarse locking. This is a performance penalty on the parallelism of the threads and might be complicated.

Alternatively, we could have getLock(...,condition,safe=F) where the ... argument is for an arbitrary collection of threadLock objects. Then, we get the lock on all of these, and when the condition is false, we release them all and iterate. Now, in this scenario, we would code things as

    getLock(a,b, condition = Quote(i == 0 && n == 1))
Now if i == 1, this is false, and so we release the lock on both a and b. Now another thread modifies n say, setting it to 3. Then we evaluate the expression again by grabbing the locks on a and b. Then, we test again. And again find this to be false. Now, suppose the other thread sets n back to 1 and "simultaneously" i to 1. Of course to do this, the other thread had to acquire the lock on both a and b. If this condition thread was waiting, it might still be waiting. Eventually, suppose it gets the locks and tests the condition. Now it is true, so it returns.

This reeks of the same style of automatically syncrhonizing input and output variables. We can parse the conditions and see what variables are involved and put them in the relevant databases.

See Also:

tryGetLock
threadLock.assign

Examples:

Thread A
   lock = threadLock("thread.count.lock", finished = F)
   tB = thread(expression)
   tC = thread(expression)       
Thread B
Thread C
    # Do some computations for this thread.
     ..
     ..
   threadLock.assign(lock,"finished",T)
   getLock(lock, condition = Quote(finished == T))
    # now wrap up the computations for this thread

Key Words:

User Level Threads

Last modified: Wed Feb 26 21:07:45 1997