preface
Learning summary of various locks in iOS for future reference, if there is any problem, please help to correct
primers
@property (nonatomic, strong) *foo is one of the most common declarations we use in daily development. We know the difference between atomic and nonatomic properties.
atomic
: atomicity, this property is default, through thesetter
,getter
Locking ensures data read and write securitynonatomic
: nonatomic, that is, unlocked. The advantage is that speed is better than useatomic
Most scenarios will not have problems
As a compiler identifier, @Property helps us to quickly generate member variables and their getters/setters. It also helps us to manage complex transactions such as memory and security through property keywords. So how does atomic help us ensure that member variables can be read and written safely? Let’s look at some code:
//@property(retain) UITextField *userName; - (UITextField *) userName {UITextField *retval = nil; @synchronized(self) { _userName = retval; }return retval;
}
- (void) setUserName:(UITextField *)userName { @synchronized(self) { [_userName release]; _userName = [userName retain]; }}Copy the code
We can easily see that the compiler uses the lock to ensure that the current _userName variable is read and written safely, so as not to generate dirty data. This is what the compiler does for us. In fact, if we dig a little deeper, it’s not really @synchronized(object).
The spin lock does not cause the thread state to change. It is always in user state, that is, the thread is always active. The thread will not enter the blocking state (sleep), reduce unnecessary context switch, fast execution
When a non-spin lock cannot be acquired, it will enter the blocking state and thus enter the kernel state. When the lock is acquired, it needs to recover from the kernel state and switch the thread context, which affects the lock performance
Why atomic is the default attribute? We can see that Apple’s design is to tell us that efficiency is worth security in many cases
How to use the lock
Consider the output in a simple piece of code
- (void)unlockTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[string appendString:@"-Locked"];
NSLog(@"% @",string);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[string appendString:@"-JailBreaked"];
NSLog(@"% @",string);
});
}
Copy the code
The purpose of writing this code is to use the variable after changing it in different threads
Console output:
2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225442] Mike- Locked-jailBreaked 2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225441] Mike-Locked-JailBreakedCopy the code
This is obviously not what we want, so how do we make sure that the variables that we use in different threads are the values that we want? One answer is to lock
NSLocking
OC offers four following classes, respectively is NSLock/NSCondtionLock/NSRecursiveLock/NSCondition, meet the needs of object-oriented programming
@protocol NSLocking - (void)lock; // Block thread, sleep thread - (void)unlock; @endCopy the code
The basic process of locking: [lock] -> [operation] -> [unlock] The four classes mentioned above can achieve this basic function, which will not be described in the following
- (void)lockedTest {
NSMutableString *string = [@"Mike"mutableCopy]; Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{// [lock]; [string appendString:@"-Locked"];
NSLog(@"% @",string); / / / lock unlock; }); Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// [lock]; [string appendString:@"-JailBreaked"];
NSLog(@"% @",string); / / / lock unlock; }); }Copy the code
Console output:
DiscoverLock_iOS[90562:11303793] Mike-Locked
DiscoverLock_iOS[90562:11303799] Mike-Locked-JailBreaked
Copy the code
DiscoverLock_iOS[90562:11303793] Mike-JailBreaked
DiscoverLock_iOS[90562:11303799] Mike-JailBreaked-Locked
Copy the code
NSConditionLock (DISPATCH_QUEUE_PRIORITY) does not guarantee the order of execution of DISPATCH_QUEUE_PRIORITY. This is a thread synchronization class
NSLock
- (BOOL)tryLock;
: Attempts to lock, returns NO on failure, does not block the thread- (BOOL)lockBeforeDate:(NSDate *)limit;
: Attempts to lock before the specified time. If NO is returned, the thread is blocked before the specified time
Sample code:
- (void)lockTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
LOCK(
[string appendString:@"-Locked"];
NSLog(@"% @",string);
sleep(5);
)
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
TRYLOCK(
[string appendString:@"-UnLock"];
NSLog(@"% @",string);
sleep(3);
)
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
TRYLOCKINDURATION(2,
[string appendString:@"-Ending"];
NSLog(@"% @",string); ) ; NSLog(@"- = - = - = - = -");
});
}
Copy the code
Console output:
2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465678] Locked 2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465679] Trylock-no 2019-11-11 19:54:08.807889+0800 DiscoverLock_iOS[92986:11465679] Mike-Locked-UnLock 2019-11-11 19:54:10.810165+0800 DiscoverLock_iOS[92986:11465677] trylockbefore-no 2019-11-11 19:54:10.810523+0800 DiscoverLock_iOS[92986:11465677] Mike- locked-unlock -Ending 2019-11-11 19:54:10.810810+0800 DiscoverLock_iOS[92986:11465677] -=-=-=-=-Copy the code
As you can see from the sample code output above, – (BOOL)tryLock; – (BOOL)lockBeforeDate (NSDate *)limit; – (BOOL)lockBeforeDate (NSDate *)limit; Blocks the thread operation before the time is up, and after waiting for the appropriate time, returns NO and executes the next print, obviously blocking the thread while it waits
The macro definitions used in the above code suggest that when using locks in the future, try to keep a clear head or simply define some convenient methods to ensure that [lock] – [unlock] pairs occur, to avoid thread blocking or deadlock situations
#define LOCK(...) \
[_lock lock]; \
__VA_ARGS__; \
[_lock unlock]; \
#define TRYLOCK(...) \
BOOL locked = [_lock tryLock]; \
NSLog(@"% @",locked? @"TryLock-YES": @"TryLock-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \
#define TRYLOCKINDURATION(duration,...) \
BOOL locked = [_lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:duration]]; \
NSLog(@"% @",locked? @"TryLockBefore-YES": @"TryLockBefore-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \
Copy the code
NSConditionLock
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
: convenience constructor that passes in the initial value of the conditional lock@property (readonly) NSInteger condition;
: Specifies the value of the current conditional lock- (void)lockWhenCondition:(NSInteger)condition;
: Performs the following operation when the conditional value of the lock is equal to the value passed in, otherwise the thread blocks- (BOOL)tryLock;
: Attempts to lock, returns NO on failure, does not block the thread- (BOOL)tryLockWhenCondition:(NSInteger)condition;
: Attempts to lock. If the condition value of the lock is equal to the value passed in, the lock succeeds. Otherwise, NO is returned and the thread is not blocked- (void)unlockWithCondition:(NSInteger)condition;
: Unlock operation and change the conditional value of the lock to the passed value- (BOOL)lockBeforeDate:(NSDate *)limit;
: Attempts to lock before the specified time. If NO is returned, the thread is blocked before the specified time- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
When the conditional value of the lock is equal to the value passed in, YES is returned if the lock is successful, otherwise NO is returned if the lock fails, and the thread is blocked before the time
Condition conditionlock – (void)lockWhenCondition:(NSInteger)condition; If condition is equal to condition at initialization, lock will be able to lock correctly. The – (void) unlockWithCondition (NSInteger) condition; Condition conditionlock is the same as NSLock if condition conditionlock is not used
We mentioned thread synchronization above, so let’s take a look at the following code
- (void)conditionLockUnordered {
NSMutableString *conditionString = [[NSMutableString alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-1-"];
NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"- 2 -"];
NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"- 3 -"];
NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"- 4 -"];
NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"- 5 -"];
NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
}
Copy the code
Console output:
2019-11-11 20:34:16.875479+0800 DiscoverLock_iOS[93895:11551560] >>> 1--2-- 3 --3 -- threadInfo:<NSThread: 0x600003905640>{number = 4, Name = (NULL)}<<< 2019-11-11 20:34:16.875525+0800 DiscoverLock_iOS[93895:11551562] >>> 3-1 --2--4--3- threadInfo:<NSThread: 0x600003903680>{number = 6, Name = (null)}<<< 2019-11-11 20:34:16.875530+0800 DiscoverLock_iOS[93895:11551561] >>> 1-1 -- 2-threadinfo :<NSThread: 0x600003908bc0>{number = 3, Name = (NULL)}<<< 2019-11-11 20:34:16.875543+0800 DiscoverLock_iOS[93895:11551559] >>> 4-1 --2--4--3- threadInfo:<NSThread: 0x6000039175c0>{number = 5, Name = (NULL)}<<< 2019-11-11 20:34:16.875628+0800 DiscoverLock_iOS[93895:11551560] >>> 5-1 --2--4--3--5- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<Copy the code
The NSLock part of the above has controlled the stability of read and write by locking, so if we want to execute according to the label, what should we do?
“Dispatch_barrier” only applies to a single concurrent queue. Dispatch_queue_t thread = dispatch_queue_create(“barrier”, DISPATCH_QUEUE_CONCURRENT); “Dispatch_get_global_queue (0,0)” is the same as “dispatch_async”
What if we want to do order in different threads? NSConditionLock conditionlock conditionlock conditionlock conditionlock conditionlock conditionlock conditionlock conditionlock conditionlock conditionlock
- (void)conditionLockOrdered {
// NSConditionLock
NSInteger conditionTag = 0;
_conditionLock = [[NSConditionLock alloc] initWithCondition:conditionTag];
NSMutableString *conditionString = [[NSMutableString alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 1 <<<");
[_conditionLock lockWhenCondition:conditionTag];
[conditionString appendString:@"-1-"];
NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 2 <<<");
[_conditionLock lockWhenCondition:1];
[conditionString appendString:@"- 2 -"];
NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:2];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 3 <<<");
[_conditionLock lockWhenCondition:2];
[conditionString appendString:@"- 3 -"];
NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:3];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@">>> handle 4 <<<");
[_conditionLock lockWhenCondition:3];
[conditionString appendString:@"- 4 -"];
NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:4];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@">>> handle 5 <<<");
[_conditionLock lockWhenCondition:4];
[conditionString appendString:@"- 5 -"];
NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlock];
NSLog(@"- = - = - = - = - = - = -");
});
NSLog(@"🍺");
}
Copy the code
Console output:
2019-11-11 20:53:58.237847+0800 DiscoverLock_iOS[94374:11586439] 🍺 2019-11-11 20:53:58.237862+0800 DiscoverLock_iOS[94374:11586488] >>> Handle 1 <<< 2019-11-11 20:53:58.237877+0800 DiscoverLock_iOS[94374:11586489] >>> Handle 3 <<< 2019-11-11 20:53:58.237868+0800 DiscoverLock_iOS[94374:11586490] >>> Handle 2 <<< 2019-11-11 20:53:58.237887+0800 DiscoverLock_iOS[94374:11586491] >>> Handle 4 <<< 2019-11-11 20:53:58.237892+0800 DiscoverLock_iOS[94374:11586495] >>> Handle 5 <<< 2019-11-11 20:53:58.238111+0800 DiscoverLock_iOS[94374:11586488] >>> 1 -1- threadInfo:<NSThread: 0x6000014c3380>{number = 3, Name = (null)}<<< 2019-11-11 20:53:58.238488+0800 DiscoverLock_iOS[94374:11586490] >>> 2-1 -- 2-threadinfo :<NSThread: 0x6000014dac40>{number = 4, Name = (null)}<<< 2019-11-11 20:53:58.238605+0800 DiscoverLock_iOS[94374:11586489] >>> 3-1 --2-- 3-threadInfo :<NSThread: 0x6000014daf00>{number = 5, Name = (NULL)}<<< 2019-11-11 20:53:58.239269+0800 DiscoverLock_iOS[94374:11586491] >>> 4-1 --2--3--4- threadInfo:<NSThread: 0x6000014c6740>{number = 6, Name = (NULL)}<<< 2019-11-11 20:53:58.239410+0800 DiscoverLock_iOS[94374:11586495] >>> 5-1 --2--3--4--5- threadInfo:<NSThread: 0x6000014c3480>{number = 7, Name = (null)} < < < 2019-11-11 20:53:58. 239552 + 0800 DiscoverLock_iOS [94374-11586495] - = - = - = - = - = - = -Copy the code
It can be seen that although different threads are scheduled at different times, due to the existence of NSConditionLock, the sequence we expected for the subsequent specific operations on data is guaranteed. I don’t see any obvious problems with this usage in cases where tasks are less time-consuming, but I recommend using dispatch_barrier if there are longer time-consuming operations because it doesn’t take up too much resources
NSRecursiveLock
- (BOOL)tryLock;
: Attempts to lock, returns NO on failure, does not block the thread- (BOOL)lockBeforeDate:(NSDate *)limit;
: Tries to lock before the specified time, if failed to return NO, to the time before the blocking thread Api sameNSLock
Exactly the same. The difference isNSRecursiveLock
The lock can be repeatedly added in the same thread without being unlocked. It will record the number of times the lock is locked and the number of times the lock is unlocked. When the two values are balanced, the lock will be released and other threads can be locked successfully
Let’s look at the next piece of code and see what the problem is:
@property (nonatomic, assign) NSInteger recursiveNum; // 5 - (void)test_unrecursiveLock { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self recursiveTest]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ self.recursiveNum = 7; NSLog(@">>> changed %ld <<<",self.recursiveNum);
});
}
- (void)recursiveTest {
if (self.recursiveNum > 0) {
self.recursiveNum -= 1;
NSLog(@">>> %ld <<<",self.recursiveNum); [self recursiveTest]; }}Copy the code
Console output:
2019-11-11 21:27:13.451703+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<< < 2019-11-11 21:27:13.451709+0800 Changed 7 <<< 2019-11-11 21:27:13.451812+0800 DiscoverLock_iOS[95105:11645279] >>> 6 <<< 2019-11-11 21:27:13.451883+0800 DiscoverLock_iOS[95105:11645279] >>> 5 <<< 2019-11-11 21:27:13.451940+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<< DiscoverLock_iOS[95105:11645279] >>> 3 <<< 2019-11-11 21:27:13.452068+0800 DiscoverLock_iOS[95105:11645279] >>> 2 <<< < 2019-11-11 21:27:13.452130+0800 DiscoverLock_iOS[95105:11645279] >>> 1 <<< DiscoverLock_iOS[95105:11645279] >>> 0 <<<Copy the code
There are two threads writing to a known recursiveNum value. One thread performs a recursive call on the value, but the other thread changes it at the same time. Without locking, this operation is problematic. A race can result in the entire logic being executed with a high probability of being incorrect.
To avoid unnecessary errors caused by this race, the first thing to think about is locking, but if you recursively lock, the thread will repeat locking, resulting in deadlocks. So you have to use recursive locking to solve this problem
- (void)test_unrecursiveLock { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self recursiveTest]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [_recursiveLock lock]; Self. RecursiveNum = 7; NSLog(@">>> changed %ld <<<",self.recursiveNum); [_recursiveLock unlock]; / / unlock}); } - (void)recursiveTest { [_recursiveLock lock]; / / recursive lockingif (self.recursiveNum > 0) {
self.recursiveNum -= 1;
NSLog(@">>> %ld <<<",self.recursiveNum); [self recursiveTest]; } [_recursiveLock unlock]; / / unlock}Copy the code
Console output:
2019-11-11 21:34:44.422337+0800 DiscoverLock_iOS[95341:11655990] >>> 4 <<< < 2019-11-11 21:34:44.422442+0800 DiscoverLock_iOS[95341:11655990] >>> 3 <<< 2019-11-11 21:34:44.422511+0800 DiscoverLock_iOS[95341:11655990] >>> 2 <<< < 2019-11-11 21:34:44.422583+0800 DiscoverLock_iOS[95341:11655990] >>> 1 <<< 2019-11-11 21:34:44.422645+0800 DiscoverLock_iOS[95341:11655990] >>> 0 <<< 2019-11-11 21:34:44.422747+0800 DiscoverLock_iOS[95341:11655992] >>> changed 7 <<< ------ 2019-11-11 21:37:11.238448+0800 DiscoverLock_iOS[95396:11662426] >>> changed 7 <<< 2019-11-11 21:37:11.238635+0800 DiscoverLock_iOS[95396:11662423] >>> 6 <<< 2019-11-11 21:37:11.238793+0800 DiscoverLock_iOS[95396:11662423] >>> 5 <<< DiscoverLock_iOS[95396:11662423] >>> 4 <<< 2019-11-11 21:37:11.239093+0800 DiscoverLock_iOS[95396:11662423] >>> 3 <<< < 2019-11-11 21:37:11.239293+0800 DiscoverLock_iOS[95396:11662423] >>> 2 <<< DiscoverLock_iOS[95396:11662423] >>> 1 <<< 2019-11-11 DiscoverLock_iOS[95396:11662423] >>> 0 <<<Copy the code
There are two outputs, but the logic of our recursive operation is completely undisturbed, and if we need to control the order, (knock on the blackboard) how do we do that?
NSCondition
- (void)wait;
: The current thread enters the sleep state immediately- (BOOL)waitUntilDate:(NSDate *)limit;
: The current thread will sleep immediately and wake up after the limit time- (void)signal;
: A single thread that goes to sleep after waking up wait- (void)broadcast;
: Wakes up wait for all threads to sleep
There are cases where you need to coordinate execution between threads. For example, one thread might need to wait for another thread to return a result, and NSCondition might be a good choice
To illustrate the role of NSCondition, let’s use a producer-consumer example that may not be very appropriate: At present, we have a flexible production line, which can only produce 3 items per batch, and it takes 6s. Meanwhile, we open the online purchasing platform to let everyone buy a group, order sales, and form a group of three people. Now, three chosen children, Tom/Mike/Lily, stand out from thousands of people around the world and successfully form a group. In order to enhance the playability, the activity starts from the opening moment, the production and the purchase at the same time, after the three pieces of inventory sales are completed, the production and the purchase at the same time again
A code example is as follows:
@interface Producer : NSObject
@property (nonatomic, assign) BOOL shouldProduce;
@property (nonatomic, strong) NSString *itemName;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector;
- (void)produce;
@end
@implementation Producer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector{
self = [super init];
if (self) {
self.condition = condition;
self.collector = collector;
self.shouldProduce = NO;
self.itemName = nil;
}
return self;
}
-(void)produce{
self.shouldProduce = YES;
while (self.shouldProduce) {
NSLog(@"Ready for production");
[self.condition lock];
NSLog(@"- p lock -");
if (self.collector.count > 0 ) {
NSLog(@"- p - wait");
[self.condition wait];
}
NSLog(@"Start production");
[self.collector addObject:@Products "1"];
[self.collector addObject:@"Item 2"];
[self.collector addObject:@"Three goods"];
NSLog(@"Production: Good 1/ Good 2/ Good 3");
sleep(6);
NSLog(@"End of production");
[self.condition broadcast];
NSLog(@"- p signal -");
[self.condition unlock];
NSLog(@"- p unlock -");
}
NSLog(@- End of production -);
}
@end
@interface Consumer : NSObject
@property (nonatomic, assign) BOOL shouldConsumer;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
@property (nonatomic, copy) NSString *itemName;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name;
- (void)consumer;
@end
@implementation Consumer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name{
self = [super init];
if (self) {
self.condition = condition;
self.collector = collector;
self.shouldConsumer = NO;
self.itemName = name;
}
return self;
}
-(void)consumer{
self.shouldConsumer = YES;
while (self.shouldConsumer) {
NSLog(@"%@- Ready to buy",self.itemName);
[self.condition lock];
NSLog(@"- c:%@ lock -",self.itemName);
if (self.collector.count == 0 ) {
NSLog(@"- c:%@ wait -",self.itemName);
[self.condition wait];
}
NSString *item = [self.collector objectAtIndex:0];
NSLog(@"% @ - buying: % @",self.itemName,item);
[self.collector removeObjectAtIndex:0];
sleep(2);
[self.condition signal];
NSLog(@"- c:%@ signal -",self.itemName);
[self.condition unlock];
NSLog(@"- c:%@ unlock -",self.itemName);
}
NSLog(@"-%@ End purchase -",self.itemName); } @end // call {NSMutableArray *pipeline = [NSMutableArray array]; NSCondition *condition = [NSCondition new]; Producer *p = [[Producer alloc] initWithConditon:condition collector:pipeline]; Consumer *c = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Tom"];
Consumer *c1 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Mike"];
Consumer *c2 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Lily"];
[[[NSThread alloc] initWithTarget:c selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:c1 selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:c2 selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:p selector:@selector(produce) object:p] start];
sleep(15);
NSLog(@"< -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- >");
p.shouldProduce = NO;
c.shouldConsumer = NO;
c1.shouldConsumer = NO;
c2.shouldConsumer = NO;
}
Copy the code
Partial console output:
2019-11-12 17:04:03.662926+0800 DiscoverLock_iOS[7110:12246052] 2019-11-12 17:04:03.662916+0800 DiscoverLock_iOS[7110:12246051] Tom- Ready to buy 2019-11-12 17:04:03.662990+0800 DiscoverLock_iOS[7110:12246053] Lily- Ready to buy 2019-11-12 17:04:03.663005+0800 DiscoverLock_iOS[7110:12246054] 2019-11-12 17:04:03.663083+0800 DiscoverLock_iOS[7110:12246053] -c :Lily lock - 2019-11-12 17:04:03.663144+0800 DiscoverLock_iOS[7110:12246053] -c :Lilywait2019-11-12 17:04:03.663254+0800 DiscoverLock_iOS[7110:12246052] -c :Mike lock - 2019-11-12 17:04:03.663439+0800 DiscoverLock_iOS[7110:12246052] - c:Mikewait-2019-11-12 17:04:03.663805+0800 DiscoverLock_iOS[7110:12246051] -c: 2019-11-12 17:04:03.663903+0800 DiscoverLock_iOS[7110:12246051] - c:Tomwait-2019-11-12 17:04:03.664126+0800 DiscoverLock_iOS[7110:12246054] -p lock - 2019-11-12 17:04:03.664297+0800 DiscoverLock_iOS[7110:12246054] Start Production 2019-11-12 17:04:03.664433+0800 DiscoverLock_iOS[7110:12246054] Produce: Commodity 1/ Commodity 2/ Commodity 3 2019-11-12 17:04:09.669735+0800 DiscoverLock_iOS[7110:12246054] Production is completeCopy the code
Based on the working principle of multi-threaded concurrency, it is also easy to reach this conclusion by printing the results in part above. Since Lily/Mike/Tom cannot meet the purchase conditions, they can only choose wait. At this time, the producer acquires the lock and executes the production code. After the production is complete, broadcast or signal tells other threads that they can wake up and continue executing the consumer-related code. The difference between NSCondition and NSConditionLock is that it relies on external values and can meet more complex demand scenarios. If you replace the broadcast of the producer with signal in the above code, there seems to be no difference between the two methods in this particular scenario. And if you are interested, you can run the code several times and see if you can come up with the same guess as the author:
- Nsconditions themselves coordinate the scheduling of tasks through queue management
- Wait tasks are queued in sequence
- Unwaited tasks are queued in the order in which they acquire locks
- Wait queues of WAIT tasks are merged into execution queues after the execution queues are complete
- The first dispatch order is determined, the execution of follow-up tasks, in accordance with the execution queue cache in turn out of the execution here only do guess, specific implementation may not be so, to the tanagers or have the opportunity to study
OSSpinLock
OSSpinLock is a spin-lock solution provided by apple before iOS10, but there is a problem with priority inversion, which was discarded by apple. OSSpinLock was used in the source code before the place. All replaced by Apple with pthread_mutex
os_unfair_lock
Os_unfair_lock os_unfair_lock is a low-level locking solution for iOS10. It is a mutex lock
void os_unfair_lock_lock(os_unfair_lock_t lock);
: lockbool os_unfair_lock_trylock(os_unfair_lock_t lock);
: Attempts to lock, returns true on success, false on failurevoid os_unfair_lock_unlock(os_unfair_lock_t lock);
: unlockvoid os_unfair_lock_assert_owner(os_unfair_lock_t lock);
: Raises assertions if the current thread does not hold the specified lockvoid os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);
: Raises assertions if the current thread holds the specified lock
Each method is not much different from the common lock, you can see the method annotation, just need to pay attention to the initialization method
{
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
}
Copy the code
@synchronize(object)
The @synchronized(object) instruction uses the passed object as the unique identifier of the lock. Only when the identifier is the same, it can meet the mutual exclusion. The advantage of the @synchronized(object) instruction to realize the lock is that we do not need to explicitly create the lock object in the code, and then we can realize the locking mechanism. And don’t worry about forgetting that unlocking is extremely common, so I won’t do the example
dispatch_semaphore
dispatch_semaphore_t dispatch_semaphore_create(long value);
: Creates a semaphore, passing in an initial valuelong dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
: When the signal <=0, the thread blocks according to the time passed in; If signal >0, the thread is not blocked and signal -1 is processedlong dispatch_semaphore_signal(dispatch_semaphore_t dsema);
: Signal +1 processing
The semaphores provided by GCD are also commonly used for locking. The common use is to initialize the signal value to 1
{ dispatch_semaphore_t lock = dispatch_semaphore_create(1); dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER); / / operation dispatch_semaphare_signal (lock); }Copy the code
Routine operations As we all know, there must be routine operations, then there must be unconventional operations, you can take a look at the AFNetwork to give us a demonstration
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
Copy the code
In AFURLSessionManager, initialize with dispatch_semaphoRE_create (0) and return tasks; Call dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); Block the thread and execute dispatch_semaphore_signal(semaphore) after the block assigns the target value. At this point, tasks has a value and the thread returns normally after being woken up. Very beautiful
pthread_mutex
The mutex scheme in C is the bottom layer of the four classes in the protocol
Lock common functions:
pthread_mutex_init
: Initializes the mutex dynamicallyPTHREAD_MUTEX_INITIALIZER
: Statically creates a mutexpthread_mutex_lock
: Locks a mutexpthread_mutex_trylock
: Locks, does not block if it failspthread_mutex_unlock
: unlockpthread_mutex_destroy
: destroy the lock
Parameter configuration functions:
pthread_mutexattr_init
: Initialization parameterpthread_mutexattr_settype
: Setting typepthread_mutexattr_setpshared
: Sets the scopepthread_mutexattr_destroy
: Destroy parameters
Conditional common functions:
pthread_cond_init
: Dynamic initialization condition quantityPTHREAD_COND_INITIALIZER
: Statically creates a conditionalpthread_cond_wait
: Pass in the conditional quantity and lockpthread_cond_signal
: Wakes up a single thread and locks itpthread_cond_broadcast
: broadcast wakes up all threadspthread_cond_destroy
: Destruction conditions
All of the above functions return a value. Note that if the function is successful, it returns 0. Otherwise, it returns an error number
The lock type:
PTHREAD_MUTEX_NORMAL
: Default value. This type of mutex does not automatically detect deadlocks. If a thread attempts to lock a mutex repeatedly, the thread will be deadlocked. Trying to unlock a mutex locked by another thread can cause unexpected results. A thread attempting to unlock a mutex that has already been unlocked can also cause unexpected resultsPTHREAD_MUTEX_ERRORCHECK
: This type of mutex automatically detects deadlocks. If a thread attempts to lock a mutex repeatedly, an error code is returned. Attempts to unlock a mutex locked by another thread will return an error code. An error code is also returned if a thread attempts to unlock a mutex that has already been unlockedPTHREAD_MUTEX_RECURSIVE
: If a thread repeatedly locks a mutex of this type, it does not cause a deadlock. If a thread repeatedly locks a mutex of this type, the same number of locks must be repeated by the thread in order to unlock the mutex, and other threads can acquire the mutex. Attempts to unlock a mutex locked by another thread will return an error code. An error code is also returned if a thread attempts to unlock a mutex that has already been unlocked. This type of mutex can only be process-private (the scoped attribute PTHREAD_PROCESS_PRIVATE)PTHREAD_MUTEX_DEFAULT
: Indicates the NORMAL type
Lock scope:
PTHREAD_PROCESS_PRIVATE
: The default value. The scope is intra-processPTHREAD_PROCESS_SHARED
: scope is interprocess
Example:
static pthread_mutex_t c_lock;
- (void)testPthread_mutex {
pthread_mutexattr_t c_lockAttr;
pthread_mutexattr_init(&c_lockAttr);
pthread_mutexattr_settype(&c_lockAttr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutexattr_setpshared(&c_lockAttr, PTHREAD_PROCESS_PRIVATE);
pthread_mutex_init(&c_lock, &c_lockAttr);
pthread_mutexattr_destroy(&c_lockAttr);
pthread_t thread1;
pthread_create(&thread1, NULL, _thread1, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, _thread2, NULL);
}
void *_thread1() {
pthread_mutex_lock(&c_lock);
printf("thread 1\n");
pthread_mutex_unlock(&c_lock);
return 0;
}
void *_thread2() {
pthread_mutex_lock(&c_lock);
printf("thread 2 busy\n");
sleep(3);
printf("thread 2\n");
pthread_mutex_unlock(&c_lock);
return 0;
}
Copy the code
Caution in using locks
-
Mutex takes time to lock and unlock. Programs that lock fewer mutex will generally run faster. Therefore, the mutex should be limited to enough, and the area protected by each mutex should be as large as possible.
-
Mutexes by their very nature are executed serially. If many threads have to manically lock the same mutex, the thread will spend most of its time waiting, which can be detrimental to performance. If the data (or code) protected by a mutex contains fragments that are unrelated to each other, a very large mutex can be broken up into several smaller mutex to improve performance. As a result, there are fewer threads requiring a small mutex at any one time, and the thread wait time is reduced. Therefore, the mutex should be large enough (to be meaningful) that each mutex should protect as little area as possible.
Reference documentation
Posix mutex pthread_mutex_t OSSpinLock How does @synchronized Lock /unlock in Objective-C? What is the difference between atomic and nonatomic? High Performance iOS App Development Chinese Version