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

Locks and multi-threaded accessors



--Apple-Mail-13-647801211
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=US-ASCII;
	format=flowed

> Here are my proposed thread safe versions:
>
> - (NSString *)title
> {
>   return [[_myTitle retain] autorelease];
> }

(I already had replied to Erik on a related question, but for the 
record...)

You have to protect _myTitle with  the same lock used in the setter. 
But the lock can and should as tight as possible, as Matt points out. 
Below is a write-up which discusses this.

Ali



Thread safety tends to be tricky.  One thing to remember when making 
getters and setters thread-safe is that it is possible for retain, 
release, or copy methods to do a lot of stuff, if overridden by the 
classes. For instance, release might end up calling dealloc, and who 
knows what that does. So it's best to try to avoid calling out too much 
while locked. It's also interesting to think about the implications of 
reentrancy; what happens if the set method is called while within the 
set method?

Here are possible thread-safe getter/setter implementations for simple 
instance variables:

  - (NSString *)title {
      id val;
      lock;
      val = [instanceVar retain];
      unlock;
      return [val autorelease];
  }

  - (void)setTitle:(NSString *)newVal {
      id val = [newVal copy];    // or retain, depending on usage
      id old;
      lock;
      old = instanceVar;
      instanceVar = val;
      unlock;
      [old release];
  }

Note that the set method here does not do an equality-check.  If this 
is desired (which is a good idea if the method is often called with the 
same value as before, and the cost of copy is higher than the cost of 
the locks), the following setter can be used instead. It simply checks 
for equality while locked, before blindly doing a copy. But, if all you 
are doing is a retain, the equality check is not worth it:

  - (void)setTitle:(NSString *)newVal {
	id val, old;

      lock;
      if (newVal == instanceVar) {
		unlock;
		return;
      }
      unlock;

      val = [newVal copy];
      lock;
      old = instanceVar;
      instanceVar = val;
      unlock;
      [old release];
  }

The only callout above while locked is in the getter, and that is a 
retain call, which is hopefully almost never a problem (release and 
copy are more likely to do more work). In the very unlikely event that 
a callback to these methods is possible from that retain callout, a 
recursive lock might be a better choice than a regular lock.

Note that the above technique applies to immutable object instance 
variables. If a class hands out mutable variables which it then can 
mutate, then we're looking at additional issues: For instance, while 
the caller is enumerating the subviews list it received from a view, if 
the view mutates the list by adding or removing a new subview, that's a 
problem.  There are several possible solutions:

1. Assure that all clients who access and cause changes to that data 
structure do it with an external lock. Basically this puts the burden 
on the caller, which is not unreasonable in many cases. In the above 
example, the caller would be responsible for using a lock or other 
means of synchronization in accessing and changing the subviews list.

2. Do not hand out the mutable instance variable, but instead return a 
copy by doing copy/autorelease in the getter. As a general solution, 
especially when used with potentially large collections, this will 
probably get expensive.  Might work for easy-to-copy mutable objects.

3. Hand out the mutable instance variable, but lazily make a copy when 
a mutation is needed. This can be accomplished either by building this 
copy-on-write behavior into the instance variable (a copy-on-write 
NSMutableArray, for instance), or by actually remembering a 
per-instance-variable flag in the object which indicates that the 
mutable instance variable was handed out and should be copied before 
being modified.

--Apple-Mail-13-647801211
Content-Transfer-Encoding: 7bit
Content-Type: text/enriched;
	charset=US-ASCII

<excerpt>Here are my proposed thread safe versions:


- (NSString *)title

{

  return [[_myTitle retain] autorelease];

}

</excerpt>

(I already had replied to Erik on a related question, but for the
record...)


You have to protect _myTitle with  the same lock used in the setter.
But the lock can and should as tight as possible, as Matt points out.
Below is a write-up which discusses this.


Ali




Thread safety tends to be tricky.  One thing to remember when making
getters and setters thread-safe is that it is possible for retain,
release, or copy methods to do a lot of stuff, if overridden by the
classes. For instance, release might end up calling dealloc, and who
knows what that does. So it's best to try to avoid calling out too
much while locked. It's also interesting to think about the
implications of reentrancy; what happens if the set method is called
while within the set method?  


Here are possible thread-safe getter/setter implementations for simple
instance variables:


<fixed> - (NSString *)title {

     id val;

     lock;

     val = [instanceVar retain];

     unlock;

     return [val autorelease];

 }


 - (void)setTitle:(NSString *)newVal {

     id val = [newVal copy];    // or retain, depending on usage

     id old;

     lock;

     old = instanceVar;

     instanceVar = val;

     unlock;

     [old release];

 }

</fixed>

Note that the set method here does not do an equality-check.  If this
is desired (which is a good idea if the method is often called with
the same value as before, and the cost of copy is higher than the cost
of the locks), the following setter can be used instead. It simply
checks for equality while locked, before blindly doing a copy. But, if
all you are doing is a retain, the equality check is not worth it:


<fixed> - (void)setTitle:(NSString *)newVal {

	id val, old;


     lock;

     if (newVal == instanceVar) {

		unlock;

		return;

     }

     unlock;


     val = [newVal copy];

     lock;

     old = instanceVar;

     instanceVar = val;

     unlock;

     [old release];

 }

</fixed>

The only callout above while locked is in the getter, and that is a
retain call, which is hopefully almost never a problem (release and
copy are more likely to do more work). In the very unlikely event that
a callback to these methods is possible from that retain callout, a
recursive lock might be a better choice than a regular lock. 


Note that the above technique applies to immutable object instance
variables. If a class hands out mutable variables which it then can
mutate, then we're looking at additional issues: For instance, while
the caller is enumerating the subviews list it received from a view,
if the view mutates the list by adding or removing a new subview,
that's a problem.  There are several possible solutions:


1. Assure that all clients who access and cause changes to that data
structure do it with an external lock. Basically this puts the burden
on the caller, which is not unreasonable in many cases. In the above
example, the caller would be responsible for using a lock or other
means of synchronization in accessing and changing the subviews list.


2. Do not hand out the mutable instance variable, but instead return a
copy by doing copy/autorelease in the getter. As a general solution,
especially when used with potentially large collections, this will
probably get expensive.  Might work for easy-to-copy mutable objects.


3. Hand out the mutable instance variable, but lazily make a copy when
a mutation is needed. This can be accomplished either by building this
copy-on-write behavior into the instance variable (a copy-on-write
NSMutableArray, for instance), or by actually remembering a
per-instance-variable flag in the object which indicates that the
mutable instance variable was handed out and should be copied before
being modified.


--Apple-Mail-13-647801211--