In these examples, we assumed that the objects (except the reference counts) never changed once they are created. If we wanted to allow the name to change, there are three possibilities:
You can make cache_lock non-static, and tell people to grab that lock before changing the name in any object.
You can provide a cache_obj_rename
which grabs
this lock and changes the name for the caller, and tell everyone to
use that function.
You can make the cache_lock protect only the cache itself, and use another lock to protect the name.
Theoretically, you can make the locks as fine-grained as one lock for every field, for every object. In practice, the most common variants are:
One lock which protects the infrastructure (the cache list in this example) and all the objects. This is what we have done so far.
One lock which protects the infrastructure (including the list pointers inside the objects), and one lock inside the object which protects the rest of that object.
Multiple locks to protect the infrastructure (eg. one lock per hash chain), possibly with a separate per-object lock.
Here is the "lock-per-object" implementation:
--- cache.c.refcnt-atomic 2003-12-11 15:50:54.000000000 +1100 +++ cache.c.perobjectlock 2003-12-11 17:15:03.000000000 +1100 @@ -6,11 +6,17 @@ struct object { + /* These two protected by cache_lock. */ struct list_head list; + int popularity; + atomic_t refcnt; + + /* Doesn't change once created. */ int id; + + spinlock_t lock; /* Protects the name */ char name[32]; - int popularity; }; static DEFINE_SPINLOCK(cache_lock); @@ -77,6 +84,7 @@ obj->id = id; obj->popularity = 0; atomic_set(&obj->refcnt, 1); /* The cache holds a reference */ + spin_lock_init(&obj->lock); spin_lock_irqsave(&cache_lock, flags); __cache_add(obj);
Note that I decide that the popularity
count should be protected by the cache_lock rather
than the per-object lock: this is because it (like the
struct list_head inside the object) is
logically part of the infrastructure. This way, I don't need to grab
the lock of every object in __cache_add
when
seeking the least popular.
I also decided that the id
member is
unchangeable, so I don't need to grab each object lock in
__cache_find()
to examine the
id
: the object lock is only used by a
caller who wants to read or write the name
field.
Note also that I added a comment describing what data was protected by which locks. This is extremely important, as it describes the runtime behavior of the code, and can be hard to gain from just reading. And as Alan Cox says, “Lock data, not code”.