iPhone use of mutexes with asynchronous URL reques

2019-02-03 05:19发布

My iPhone client has a lot of involvement with asynchronous requests, a lot of the time consistently modifying static collections of dictionaries or arrays. As a result, it's common for me to see larger data structures which take longer to retrieve from a server with the following errors:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.'

This typically means that two requests to the server come back with data which are trying to modify the same collection. What I'm looking for is a tutorial/example/understanding of how to properly structure my code to avoid this detrimental error. I do believe the correct answer is mutexes, but I've never personally used them yet.

This is the result of making asynchronous HTTP requests with NSURLConnection and then using NSNotification Center as a means of delegation once requests are complete. When firing off requests that mutate the same collection sets, we get these collisions.

6条回答
男人必须洒脱
2楼-- · 2019-02-03 05:35

N.B. If you are using synchronisation don't forget to add -fobjc-exceptions to your GCC flags:

Objective-C provides support for thread synchronization and exception handling, which are explained in this article and “Exception Handling.” To turn on support for these features, use the -fobjc-exceptions switch of the GNU Compiler Collection (GCC) version 3.3 and later.

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html

查看更多
一夜七次
3楼-- · 2019-02-03 05:36

Use a copy of the object to modify it. Since you are trying to modify the reference of an array (collection), while someone else might also modify it (multiple access), creating a copy will work for you. Create a copy and then enumerate over that copy.

NSMutableArray *originalArray = @[@"A", @"B", @"C"];
NSMutableArray *arrayToEnumerate = [originalArray copy];

Now modify the arrayToEnumerate. Since it's not referenced to originalArray, but is a copy of the originalArray, it won't cause an issue.

查看更多
祖国的老花朵
4楼-- · 2019-02-03 05:38

In response to the sStaticData and NSLock answer (comments are limited to 600 chars), don't you need to be very careful about creating the sStaticData and the NSLock objects in a thread safe way (to avoid the very unlikely scenario of multiple locks being created by different threads)?

I think there are two workarounds:

1) You can mandate those objects get created at the start of day in the single root thread.

2) Define a static object that is automatically created at the start of day to use as the lock, e.g. a static NSString can be created inline:

static NSString *sMyLock1 = @"Lock1";

Then I think you can safely use

@synchronized(sMyLock1) 
{
  // Stuff
}

Otherwise I think you'll always end up in a 'chicken and egg' situation with creating your locks in a thread safe way?

Of course, you are very unlikely to hit any of these problems as most iPhone apps run in a single thread.

I don't know about the [Group semaphore] suggestion earlier, that might also be a solution.

查看更多
Root(大扎)
5楼-- · 2019-02-03 05:38

There are other ways if you don't want the overhead of Locking as it has its cost. Instead of using a lock to protect on shared resource (in your case it might be dictionary or array), you can create a queue to serialise the task that is accessing your critical section code. Queue doesn't take same amount of penalty as locks as it doesn't require trapping into the kernel to acquire mutex. simply put

 dispatch_async(serial_queue, ^{
    <#critical code#>
})

In case if you want current execution to wait until task complete, you can use

dispatch_sync(serial_queue Or concurrent, ^{
    <#critical code#>
})

Generally if execution doest need not to wait, asynchronous is a preferred way of doing.

查看更多
霸刀☆藐视天下
6楼-- · 2019-02-03 05:53

If it's possible that any data (including classes) will be accessed from two threads simultaneously you must take steps to keep these synchronized.

Fortunately Objective-C makes it ridiculously easy to do this using the synchronized keyword. This keywords takes as an argument any Objective-C object. Any other threads that specify the same object in a synchronized section will halt until the first finishes.

-(void) doSomethingWith:(NSArray*)someArray
 {    
    // the synchronized keyword prevents two threads ever using the same variable
    @synchronized(someArray)
    {
       // modify array
    }
 }

If you need to protect more than just one variable you should consider using a semaphore that represents access to that set of data.

// Get the semaphore.
id groupSemaphore = [Group semaphore];

@synchronized(groupSemaphore) 
{
    // Critical group code.
}
查看更多
对你真心纯属浪费
7楼-- · 2019-02-03 05:58

There are several ways to do this. The simplest in your case would probably be to use the @synchronized directive. This will allow you to create a mutex on the fly using an arbitrary object as the lock.

@synchronized(sStaticData) {
  // Do something with sStaticData
}

Another way would be to use the NSLock class. Create the lock you want to use, and then you will have a bit more flexibility when it comes to acquiring the mutex (with respect to blocking if the lock is unavailable, etc).

NSLock *lock = [[NSLock alloc] init];
// ... later ...
[lock lock];
// Do something with shared data
[lock unlock];
// Much later
[lock release], lock = nil;

If you decide to take either of these approaches it will be necessary to acquire the lock for both reads and writes since you are using NSMutableArray/Set/whatever as a data store. As you've seen NSFastEnumeration prohibits the mutation of the object being enumerated.

But I think another issue here is the choice of data structures in a multi-threaded environment. Is it strictly necessary to access your dictionaries/arrays from multiple threads? Or could the background threads coalesce the data they receive and then pass it to the main thread which would be the only thread allowed to access the data?

查看更多
登录 后发表回答