Overview
In multi-threaded programming, we often encounter multiple threads accessing shared resources at the same time. We need to avoid this by synchronizing threads. That is, lock the thread.
Because Objective-C is a superset of C. Strictly speaking, it is a true superset. Therefore, pthread mutex in C language can also be used in Objective-C, but Objective-C defines its own lock object and lock protocol, so this article introduces locks in Objective-C.
NSLock NSLocking protocol
@protocol NSLocking
-(void) lock;
-(void) unlock;
@end
The following types of locks, NSLock, NSConditionLock, and NSRecursiveLock, all implement this protocol to uniformly lock and unlock with lock and unlock.
NSLock use
NSLock * lock = [[NSLock alloc] init];
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
[lock lock]; // Get lock
NSLog (@ "lock success");
sleep (5);
NSLog (@ "sleep end");
[lock unlock]; // Abandon the lock acquired before
});
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
[lock lock]; // Obtain a lock, if you can't get it, block the current thread until you get the lock
NSLog (@ "remove lock");
[lock unlock]; // Abandon the acquired lock
});
Output result
lock success
sleep end
remove lock
When the NSLock object sends a lock message, first check whether the lock can be obtained. If the lock is being used in other threads at this time, then the current thread will be blocked until the other threads have used the lock. After the lock is unlocked, the current lock can be obtained. The thread is reacquired and the thread resumes. If the lock cannot be acquired, the thread will always block, so the two methods lock and unlock must appear in pairs. Of course, there are thread methods tryLock and lockBeforeDate that do not block calling the lock:
tryLock
tryLock tries to acquire the lock, if it can't get NO, it returns YES, otherwise it won't block the thread calling it
NSLock * lock = [[NSLock alloc] init];
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
[lock lock]; // Get lock
NSLog (@ "lock success");
sleep (5);
NSLog (@ "sleep end");
[lock unlock]; // Abandon the lock acquired before
});
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
if ([lock tryLock]) // Try to acquire the lock, if the acquisition fails to return NO, the thread will not be blocked
{
NSLog (@ "remove lock");
[lock unlock];
}
else {
NSLog (@ "obtain failure");
}
});
Output result
lock success
obtain failure
sleep end
The acquisition failed because the current lock is being used in another thread. If the current lock is not used then YES is returned.
lockBeforeDate
lockBeforeDate blocks the calling thread, if the lock cannot be acquired within a given time then gives up acquiring and resumes the thread.
NSLock * lock = [[NSLock alloc] init];
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
[lock lock]; // Get lock
NSLog (@ "lock success");
sleep (5);
NSLog (@ "sleep end");
[lock unlock]; // Abandon the lock acquired before
});
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
NSDate * date = [[NSDate alloc] initWithTimeIntervalSinceNow: 3];
if ([lock lockBeforeDate: date]) // Try to acquire a lock in the next 3s and block the thread, if the recovery thread cannot be obtained within 3s
{
NSLog (@ "remove lock");
[lock unlock];
}
else {
NSLog (@ "obtain failure");
}
});
Output result
lock success
obtain failure
sleep end
NO if not available within 3s, otherwise YES
@synchronized
synchronized provides a locking and unlocking mechanism for an object. synchronized will block the calling thread
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
@synchronized (self) {
[self logging: @ "lock success"];
sleep (5);
[self logging: @ "sleep end"];
}
});
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
@synchronized (self) // Wait to execute the following code after the execution of the above thread is completed, and the current thread will be blocked at this time
{
[self logging: @ "remove lock"];
}
});
Output result
lock success
sleep end
remove lock
The above locking of its own object will not execute the following locked code until the first GCD has been completely executed.
note
Objective-C classes themselves are objects, so you can use synchronized for these class objects, and at this time, thread synchronization is performed on the entire object type. E.g:
@synchronized ([self class]) {
}
NSConditionLock
NSConditionLock conditional lock. When acquiring the lock, it must be the same as the unlocking condition previously set. Otherwise, the current thread will block until the conditions are consistent.
The most typical example is the producer-consumer scenario. When the data is empty, the producer produces data. At this time, the consumer cannot obtain the data. After the producer produces the data, the consumer processes the data until the consumer processes all the data. It is empty again, at this time the producer continues to produce data. The sample code is as follows:
enum {NO_DATA_IN_QUEUE = 0, HAS_DATA_IN_QUEUE};
NSConditionLock * conditionLock = [[NSConditionLock alloc] initWithCondition: NO_DATA_IN_QUEUE];
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
while (YES) {
[conditionLock lockWhenCondition: NO_DATA_IN_QUEUE];
NSLog (@ "insert data");
sleep (3);
NSLog (@ "intset success");
[conditionLock unlockWithCondition: HAS_DATA_IN_QUEUE];
}
});
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
while (YES) {
[conditionLock lockWhenCondition: HAS_DATA_IN_QUEUE];
NSLog (@ "delete data");
sleep (3);
NSLog (@ "delete success");
[conditionLock unlockWithCondition: NO_DATA_IN_QUEUE];
}
});
Output results:
insert data
intset success
delete data
delete success
insert data
intset success
delete data
delete success
...
NSRecursiveLock
NSRecursiveLock recursive lock. When we synchronize a thread with a recursive function, the lock will be acquired multiple times in the same thread, resulting in thread deadlock. NSRecursiveLock can acquire the lock multiple times in the same thread without deadlock.
NSRecursiveLock * recursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async (dispatch_get_global_queue (0, 0), ^ {
static void (^ recursive) (int count);
recursive = ^ (int count) {
[recursiveLock lock];
if (count> 0) {
NSLog (@ "success lock");
sleep (3);
recursive (-count);
}
[recursiveLock unlock];
};
recursive (3);
});
In the above case, if you use NSLock to continue to acquire the lock without unlocking it, it will cause a deadlock and cause the threads to be consistently blocked.
NSDistributedLock
NSDistributedLock is a mutex solution in Mac multi-threaded development, so I won't introduce more here.
Synchronous thread locks in Objective-C