6.4. Protecting The Objects Themselves

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:

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:

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 spinlock_t cache_lock = SPIN_LOCK_UNLOCKED;
@@ -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".