|
Lameness Disclaimer: All this is written to the best of my knowledge. Corrections, additions etc. are certainly welcome.
Concurrent Programming, a quick refresherAs you know, you should protect shared mutable data with locks as explained in Cocoa/TasksAndConcepts/ProgrammingTopics/Multithreading. Even just incrementing a global variable
is not thread safe at all. This one C-instruction is actually executed with three or more PPC instructions that look similar or identical to this:
While each PPC instruction is executed there is a possibility that an interrupt occurs. The processing of this thread will then be suspended and the processor's attention will be focused on another thread. This other thread might very well now execute the same code trying to access the same global variable! To show how this can be disastrous, lets examine two cases where two threads execute the same code. In the first example the context switch to the second thread is harmless, because the increment statements have completed:
but it could also happen, that the context switch occurs right in the middle of the increment instruction sequence:
As you can see the outcome is not the same!
Using Foundation to share common resourcesThe general solution to share a resource between threads is to use a lock (aka mutex/semaphore). Foundation provides the NSLock class family (NSLock, NSRecursiveLock, NSConditionLock, NSDistributedLock) for that purpose. The next code shows a relevant part of a hypothetical NSMutableDictionary subclass, that achieves thread safety with the use of a lock:
Noteworthy about this code is, that there are two calls to the lock and that we need to catch exceptions if they are raised in the critical section, enclosed by the NSLock calls. It could be disastrous to not catch the exception, because then the lock would remain locked and the application would deadlock the next time the method would be called (1). We can optimize a little using IMPs (see previous articles). This doesn't just look slow, it turns out to be slow; as running a test case on this shows. In the test case I used, the thread safe code ran in 9.2 s whereas the regular code took 5.2 s. (Without the NS_DURING the test code clocked in at 7.0 s. Using IMPs and no NS_DURING 6.8 s). So routines, that need to be thread safe and are called often, can prove to be a burden on your performance!
If you want to discuss this articles, please do so in this thread in the Mull e kybernetiK Optimization Forum. (1)In this case, an exception will only be raised if object is nil. So that exception could be avoided by checking before hand and the NS_DURING could be left out. |