Destroying actors cleanly

Destroying an actor cleanly in a multithread environment requires storing the doomed object in a global queue until determining the object is not involved in other processes. Only after an object is removed from the mover and from refresh threads can it be deleted from memory. The whole process is managed by the universe thread.

Queuing an actor for deletion

When TCollisionBehavior determines an actor should be destroyed, it sends a SelfDestruct call to the object. The actor announces its own demise through synchronous notification and gets added to the global queue.


Deleting queued actors

Any time there's an actor in the queue, the universe thread attempts to execute. The thread processes all deletions (and creations) in the order they were queued. When the queue is empty, the universe thread returns to a blocked state.


Pausing the mover and refresh threads

To pause the mover and refresh threads, the universe thread waits until the other threads stop executing and they go to a blocked state. The universe thread then acquires a synchronization lock, or semaphore, on each thread.

The code for TPeriodic::BoundRun shows when mover and refresh threads
are vulnerable.

      void
      TPeriodicThread::BoundRun()
      {
          fLock.Acquire();
          TTime timeSleptThisInterval(TTime::kZero);
          TTime delayInterval(TSeconds(0));
          TStopwatch watch;
          watch.Start();
          while(fDone == FALSE) {
              watch.GetElapsedTimeAndReset(timeSleptThisInterval);
              if (timeSleptThisInterval >= delayInterval) {
                  HandleIntervalPassed(timeSleptThisInterval);
                  GetDelayInterval(delayInterval);
                  watch.GetElapsedTimeAndReset(timeSleptThisInterval);
              } 
              delayInterval -= timeSleptThisInterval;
      
              fLock.Release();
              try {
                  fDelay.DelayFor(delayInterval);
              } 
              catch(TClockException& err) {
                  if (err != TClockException(TClockException::kDelayCanceled)) {
                      TPeriodicThreadEntry entry(this);
                      fDone = TRUE;
                      watch.Stop();
                      throw;
                  }
              }
              fLock.Acquire();
              
          }
          watch.Stop();
          fLock.Release();
      }
Once the universe thread has locks on the mover and the refresh threads, it can safely delete the queued actor, knowing that actor isn't being used by the other threads. The mover and refresh threads can only reacquire their locks--and resume executing--after the universe thread sends resume calls.

The mover and refresh threads aren't often blocked, because they are usually inactive during the time the universe thread holds their locks. Because blocking happens, this program optimizes the user's view of the program by pausing the refresh thread last (and resuming it first).

Locking the thread, not the data

Although semaphores are usually used to lock data, this program uses them to block threads. It is possible to place locks on all the data, but this approach requires locking data every time it's touched. The management and maintenance overhead for this approach is a burden, and not necessary for
this
program.


[Contents] [Previous] [Next]
Click the icon to mail questions or corrections about this material to Taligent personnel.
Copyright©1995 Taligent,Inc. All rights reserved.

Generated with WebMaker