[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Restoring objects



>bill@cambridge.apple.com (Bill St. Clair) writes:
>
>> 
>> The only drawback with this is that no instance of cleanup-mixin
>> will be gc'able. You can solve this by using weak hash tables
>> (undocumented, but present in both 2.0b1 and 2.0 final).
>
>What are "weak hash tables", how do they work, and what are they good
>for?

I hesitate to say much about them as they are undocumented and
likely to stay that way. They are part of 2.0b1 and will be a part of
2.0 final, but we may change the interface in future releases.

That said, I'll explain what they are.

Sometimes you don't want an object's presence in a hash table to
prevent that object from being garbage collected. You want to
associate the object with another object, but remove the association
if the object ever becomes unreachable by anything but MAPHASH
(or WITH-HASH-TABLE-ITERATOR).

One use for this is associating in-memory objects with their addresses
in a persistent object database. If the in-memory object becomes garbage
except for its presence in the hash table, you would usually prefer to
reload it from the database rather than allowing it to occupy RAM.

Another use is the one that started this thread: you can use a weak
hash table to store all of the instances of a given class so that
you can maphash over them later. Because the hash table is weak,
it will not prevent instances from being garbage collected.

A weak hash table can be weak on :KEY or :VALUE. Hash tables which are
weak on :KEY must be EQ hash tables. If a key in such a hash table
becomes unreachable, its KEY/VALUE pair will be removed from the hash
table. If the value in a hash table which is weak on :VALUE becomes
unreachable, its KEY/VALUE pair will be removed from the hash table.

A weak hash table is created by the :WEAK keyword to MAKE-HASH-TABLE.
It's value can be :KEY or :VALUE.

Examples:

Weak on key
-----------

? (defparameter *h* (make-hash-table :test 'eq :weak :key))
*H*
? (defparameter *key* (list 'key))
*KEY*
? (setf (gethash *key* *h*) 'value)
VALUE
? (setf (gethash 'key2 *h*) 'value2)
VALUE2
? (maphash #'(lambda (key value) (print (list key value)))
           *h*)

((KEY) VALUE) 
(KEY2 VALUE2) 
NIL
? (setq *key* nil)
NIL
? (gc)
NIL
? (maphash #'(lambda (key value) (print (list key value)))
           *h*)

(KEY2 VALUE2) 
NIL


Weak on value
-------------

? (defparameter *h* (make-hash-table :test 'equal :weak :value))
*H*
? (progn (setf (gethash 'key *h*) (list 'value)) nil)
NIL
? (setf (gethash 'key2 *h*) 'value2)
VALUE2
? (maphash #'(lambda (key value) (print (list key value)))
           *h*)

(KEY (VALUE)) 
(KEY2 VALUE2) 
NIL
? (gc)
NIL
? (maphash #'(lambda (key value) (print (list key value)))
           *h*)

(KEY2 VALUE2) 
NIL
? 


There are a few things that can cause unexpected results while playing
with weak hash tables in the Listener.

The variables *, **, ***, /, //, /// can often prevent a key or
value from being garbage collected. That is the reason for the
(progn (setf (gethash ...) ...) nil) in the second example above.

A hash table has a single element cache which is set by gethash,
(setf (gethash ...) ...), and maphash. If a key or value is in
the cache, it won't be garbage collected. That is the reason I
put a second key in each of the hash tables in the example (and
lucked out that the second key was the last one encountered by
maphash, hence the one that was left in the hash table's cache).