However, since one of the active computations is the current blocked thread, the number of active computations will never reach zero.
To handle this situation, objects 14 are forbidden to delete themselves unless they can guarantee that the current thread 5 is the only active computation (or there are no active computations on the object). Instead, of calling delete( ), the
object calls its object surrogate's mark for delete( )
method. The mark for delete( ) method will cause the
object surrogate 16 to consider the object 14 it refers to as 1° invalid. Note, this cannot be accomplished by calling the invalidate( ) method as this would lead to the deadlock
which the mark for delete( ) method prevents. The object
surrogate 16 also sets a flag within itself (delete on exit in
FIG. 2). When the count of active computations reaches :5
zero, if the delete on exit flag is set, the object 14 is
deleted.
FIG. 3 shows a trace of a use of mark for deleteo within
an active computation. In step 26, thread X I creates an ActiveComputation object 18 (FIG. 2) on the stack. In step 20 28, the ActiveComputation object's 18 constructor calls the
surrogate's 16 add computation( ) method. The add
computation( ) method increments the number of active computations marking the beginning of an active computation X5 (FIG. 1). In step 30, sometime later, but within the 25 lifetime of the ActiveComputation object 18 (i.e., the current
block), thread XI invokes the object surrogate's 16 valid
object( ) method which returns a pointer to the object 14. Step 32, still later, but again within the lifetime of the ActiveComputation object 18, thread XI invokes a 30 "suicide"( ) method in object 14. The suicide s method is an example of a method in which the object 14 will destroy itself. In step 34, within the suicide method, the object 14
calls its surrogate's 16 mark for delete( ) method. Since
there is at least one active computation X5, the object 14 35 cannot be deleted yet.
Instead, the surrogate 16 records the request and returns. The implementation must ensure that a subsequent call to
valid object( ) will not return a pointer to the object 14. The 4Q
"suicide( )" method then returns in step 36. In step 38, the block enclosing the ActiveComputation object 18 is exited. This causes the ActiveComputation's 18 destructor to fire
which calls the surrogate's 16 delete computation( )
method in step 40. This ends this active computation X5. 4J Assuming this was the only active computation at this point, the surrogate's 16 count of active computations will now be zero. Since the object 14 had requested that it be deleted, the surrogate 16 will now do so in step 42 and clear its flag to ensure that the object is not deleted twice should another 5Q computation be created and destroyed. Deleting the object will invoke the object's destructor which in step 44 will call the surrogate's 16 invalidate( ) method. Since there are no active computations executing, the invalidate( ) method will return. 55
If there had been other active computations on the object 14 in step 40, then the count would not have gone to zero and the surrogate 16 would not have deleted the object 14. The deletion would be postponed until the final active computation had completed and the count had gone to zero. 60
In a multi-threaded environment correctness is dependent on the accuracy of the reference count and active computation count. Traditionally, such accuracy is achieved by bracketing the operations on the count with the lock and unlock of a mutual exclusion object. In practice, a mutex 65 guarded counter may impose performance penalties which are unacceptable for some time- critical applications.
6
An alternative to a mutex guarded counter is a probabilistic counter. While this cannot guarantee that the count is accurate, the likelihood of it being inaccurate can be made arbitrarily small. In one embodiment, as shown in FIG. 4 a probabilistic 49 counter is implemented as an array 52 of non-negative integers (or equivalent data structures). These integers are initialized to zero. To increment the probabilistic counter, an index of the array is selected randomly (or pseudo- randomly) and the associated integer is incremented. The increment method returns to its caller an opaque token containing the index which was used. To decrement the probabilistic counter, the original token is handed back and the associated integer is decremented. The value of the probabilistic counter is considered the sum of the integers. In most multi-threaded environments, the sum cannot be performed atomically without requiring some additional mutual exclusion mechanism (which is what the probabilistic counter is trying to avoid).
For the purpose of maintaining an object's surrogate's 16 count of active computations X5, however, five simplifying assumptions can be made. First, the only requirement on the counter is that it be eventually detectable that it has hit zero. Second, the only time that the value of the counter is
checked is on an invalidate( ) call or on a delete
computation( ) when the mark for delete flag has been set.
Third, a thread XI cannot request valid object( ) until it has
completed calling add computation( ). Fourth, once the
surrogate 16 has flagged its object 14 as invalid, the surrogate will never refer to a valid object again. Fifth, when
mark for delete( ) and invalidate( ) are called, they first
cause the surrogate 16 to consider its associated object invalid. These assumptions are sufficient to guarantee correctness in the absence of thread swap causing an incorrect addition in one of the underlying integers.
Consider the situation in which mark for delete( ) has
previously been called on a surrogate 16. By assumption 3
and 4, threads which call valid object( ) on this surrogate
16 will always receive a null pointer. If there is currently exactly one active computation X5, all but one of the integers will be zero and the remaining integer will have a
value of 1. When delete computation( ) is called, three
situations must be considered. In scenario 1, the call to
delete computation( ) completes without an intervening
add computation( ). In this case, the summation of the
integers within delete computation( ) will discover that the
integers are all zero and the object 14 will be deleted.
In scenario 2, during delete computation( ), add
computation( ) is called by another thread X2 and the integer chosen to increment has not yet been considered as part of
the sum being computed by the delete computation( ). In
this case, the delete computation( ) will consider that there
is still one active computation and will not consider the sum 0. Thus the object 14 will not deleted until this new computation X6 calls delete computation( ).
In scenario 3, during delete computation( ), another
thread X2 calls add computation( ) and the integer chosen
to be incremented has already been considered in the summation in delete computation( ). In this case, the count will
be considered to be 0, and the object 14 will be deleted. This does not cause problems because given the assumptions (3 & 4) it is impossible for the new computation X6 to obtain a pointer to the deleted object 14. It is imperative, however, that the surrogate 16 be implemented to not attempt to delete the object 14 when this new computation concludes. This
can be accomplished by clearing the mark for delete flag
or clearing the pointer 13a.
In an alternative embodiment, the integers 52 need not be non-negative integers and the integer chosen for add