Multithreading series chapter plan content: IOS multithreaded programming (a) multithreaded foundation iOS multithreaded programming (two) Pthread iOS multithreaded programming (three) NSThread iOS multithreaded programming (four) GCD iOS multithreaded programming (five) GCD underlying principle iOS multithreaded programming (six) NSOperation IOS multithreaded programming (7) Synchronization mechanism and lock iOS multithreaded programming (8) RunLoop
NSThread is an object-oriented lightweight multithreading solution provided by Apple. An NSThread object represents a thread. It is relatively simple to use, but requires manual management of the thread life cycle and handling of thread synchronization.
1. Create and start the NSThread
- Creating an NSThread thread has class methods and instance methods.
Class method creation:
+ (void)detachNewThreadWithBlock:(void(^) (void))block ;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
Copy the code
Instance method creation:
- (instancetype)initWithBlock:(void(^) (void))block;
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
Copy the code
Using the instance method to create a thread returns a thread object, and you can set the corresponding property parameters as needed.
Note: the block creation mode must be used after iOS10
- Don’t forget to start the thread after you create it!
After the thread is created, we need to call the start method to start the thread (the thread created using the class method implicitly started the thread), otherwise the thread will not execute.
However, after the class method is created or the instance method is created and the start method is called, the thread does not execute immediately. Instead, the thread is added to the pool of schedulable threads and put into a ready state, waiting for the CPU to schedule the execution. (See thread life cycle in Multithreading Basics for thread states.)
2. NSThread Thread attributes
name
Property: Sets the name of the thread
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(Thread: % @start[NSThread currentThread]);
}];
thread.name = @" Test thread";
[thread start];
Copy the code
The print result is as follows:
Thread: < NSThread:0x600001227200>{number = 6, name = test thread} startCopy the code
qualityOfService
Property: Sets thread priority
The threadPriority attribute is a double and ranges from 0.0 to 1.0. The higher the value, the higher the priority. However, this property has been replaced by qualityOfService. QualityOfService is an enumerated value. The definition is as follows:
typedef NS_ENUM(NSInteger.NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21.NSQualityOfServiceUserInitiated = 0x19.NSQualityOfServiceUtility = 0x11.NSQualityOfServiceBackground = 0x09.NSQualityOfServiceDefault = - 1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
Copy the code
NSQualityOfServiceUserInteractive highest priority, from top to bottom in turn reduce, NSQualityOfServiceDefault as the default priority.
Use as follows:
NSThread *thread1 = [[NSThread alloc] initWithBlock:^{
NSLog(@"\n thread: % @start"[NSThread currentThread]);
}];
thread1.name = @" Test thread 1";
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithBlock:^{
NSLog(@"\n thread: % @start"[NSThread currentThread]);
}];
thread2.qualityOfService = NSQualityOfServiceUserInteractive;
thread2.name = @" Test thread 2";
[thread2 start];
Copy the code
Although thread1 before thread2 start, thread1 priority as the default, and thread2 priority for NSQualityOfServiceUserInteractive, when executed, thread2 before thread1 is carried out.
Thread: < NSThread:0x600001e557c0>{number = 7, name = test thread2} start thread: <NSThread:0x600001e55700>{number = 6, name = test thread1 } start
Copy the code
callStackReturnAddresses
andcallStackSymbols
Properties:
The callStackReturnAddresses attribute is defined as follows:
@property (class.readonly.copy) NSArray<NSNumber *> *callStackReturnAddresses
Copy the code
This property returns an array of virtual addresses for function calls in the thread.
The callStackSymbols property is defined as follows:
@property (class.readonly.copy) NSArray<NSString *> *callStackSymbols
Copy the code
This property returns the thread-called function as a symbol.
CallStackReturnAddress and callStackSymbols can be used together with NSLog to track function calls of threads, which is an important means of programming and debugging.
threadDictionary
Properties:
Each thread has its own stack space and maintains a key-value dictionary that can be accessed from anywhere in the thread. You can use this dictionary to store information that remains constant throughout the execution of a thread. For example, you can use it to store the state of multiple iterations of the Run loop throughout your thread.
- Other attributes
@property (class.readonly.strong) NSThread *mainThread; // Get the main thread
@property (class.readonly.strong) NSThread *currentThread;// Get the current thread
@property NSUInteger stackSize; // Threads use stack size, default 512K
@property (readonly) BOOL isMainThread; // Is the main thread
@property (class.readonly) BOOL isMainThread ; // reports whether current thread is main
@property (readonly.getter=isExecuting) BOOL executing ; // Whether the thread is executing
@property (readonly.getter=isFinished) BOOL finished ; // Whether the thread is finished executing
@property (readonly.getter=isCancelled) BOOL cancelled; // Whether to cancel the thread
Copy the code
3. The NSThread is blocked
NSThread provides two class methods,
+ (void)sleepUntilDate:(NSDate *)date; // Hibernate until the specified date
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// Hibernate execution time
Copy the code
For the example code above that sets thread priority, let’s make a few changes.
NSThread *thread1 = [[NSThread alloc] initWithBlock:^{
NSLog(@"\n thread: % @start"[NSThread currentThread]);
}];
thread1.name = @" Test thread 1";
[thread1 start];
// add the sleep function
[NSThread sleepForTimeInterval:1];
NSThread *thread2 = [[NSThread alloc] initWithBlock:^{
NSLog(@"\n thread: % @start"[NSThread currentThread]);
}];
thread2.qualityOfService = NSQualityOfServiceUserInteractive;
thread2.name = @" Test thread 2";
[thread2 start];
Copy the code
Add NSThread sleepForTimeInterval:1 between thread1 and thread2. Let the main thread block for 1 second, and thread1 will execute before Thread2, even if thread2 has a higher priority than Thread1.
When CPU time arrives, only thread1 is in the schedulable thread pool. Thread1 is scheduled to execute. When CPU time ends, Thread2 enters the ready state and is scheduled to execute at the next CPU time.
4. Termination of NSThread
- Cancel the thread
- (void)cancel ;
Copy the code
A thread that has been scheduled cannot be cancelled with cancel.
- Out of the thread
+ (void)exit;
Copy the code
Force the thread to exit, causing the thread to enter the death state.
5. Thread communication
In development, we sometimes need to perform time-consuming operations on the child thread and then switch to the main thread to refresh the UI. This involves communication between threads, and NSThread threads provide extension functions to NSObject.
5.1 NSObject way
// Execute wait on the main thread to block the method until the main thread is idle. Modes indicate kCFRunLoopCommonModes
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
// equivalent to the first method with kCFRunLoopCommonModes
// Perform the operation on the specified thread
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
// Implicitly create a thread and execute
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
// the NSObject function performs the operation on the current thread, calling NSObject's performSelector: related method
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
Copy the code
As an example, let’s simulate the implementation of a child thread downloading an image and returning to the thread to refresh the UI
// open child threads to simulate network requests
- (void)downloadImage {
[NSThread detachNewThreadWithBlock:^{
// 1. Get imageUrl
NSURL *imageUrl = [NSURL URLWithString:@"https://xxxxx.jpg"];
// 2. Read data from imageUrl (download image) -- time consuming operation
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
// Create an image from binary data
UIImage *image = [UIImage imageWithData:imageData];
// Main thread refreshes the UI
[self performSelectorOnMainThread:@selector(mainThreadRefreshUI) withObject:image waitUntilDone:YES];
}];
}
// The main thread refreshes the UI call method
- (void)mainThreadRefreshUI:(UIImage *)image {
self.imageView.image = image;
}
Copy the code
5.2 Port Communication Mode
Port communication needs to use NSPort, NSPort is an abstract class, you can use its subclass NSMachPort.
The following method is used to pass information data that will be communicated between threads.
- (BOOL)sendBeforeDate:(NSDate *)limitDate components:(nullable NSMutableArray *)components from:(nullable NSPort *) receivePort reserved:(NSUInteger)headerSpaceReserved;
- (BOOL)sendBeforeDate:(NSDate *)limitDate msgid:(NSUInteger)msgID components:(nullable NSMutableArray *)components from:(nullable NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;
Copy the code
Implement the NSPortDelegate method to accept data from the port.
- (void)handlePortMessage:(NSPortMessage *)message
Copy the code
Note: When using a port, be careful to include the port in the current Runloop, otherwise the message will not be delivered
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode]; Copy the code
6. NSThread notice
NSWillBecomeMultiThreadedNotification: sent when the first other thread is spawned from the current thread, usually only once per threadNSDidBecomeSingleThreadedNotification: This notification is currently meaningless and can be ignoredNSThreadWillExitNotificationSend this notification before the thread exitsCopy the code
7. NSThread Thread security case
It is possible to have non-thread-safe situations whenever multithreading is involved. The root cause is that multiple threads operate a critical area at the same time, resulting in resource disorder of critical area.
Let’s simulate the classic case of multi-threaded ticket sales: two ticket Windows selling 50 tickets at the same time
- (void)initTicketStatusNotSave {
// 1. Set the remaining train ticket to 50
self.ticketSurplusCount = 50;
// 2. Simulate window 1 ticket thread
self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil];
self.ticketSaleWindow1.name = @" Ticket window 1";
// 3. Simulate window 2 ticket thread
self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil];
self.ticketSaleWindow2.name = @" Ticket Window 2";
// 4. Start selling train tickets
[self.ticketSaleWindow1 start];
[self.ticketSaleWindow2 start];
}
/** * Sell train tickets (not thread safe) */
- (void)saleTicketNotSafe {
while (1) {
// If tickets are available, go on sale
if (self.ticketSurplusCount > 0) {
self.ticketSurplusCount --;
NSLog(@ "% @"[NSString stringWithFormat:@" Remaining votes: %ld window: %@".self.ticketSurplusCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}
// If the ticket is sold out, close the ticket window
else {
NSLog(@" All train tickets sold out");
break; }}}Copy the code
The partial results are as follows:
2020- 11- 19 15:55:53.222575+0800 pthread[5018:211393] Remaining votes:49Window: ticket window1
2020- 11- 19 15:55:53.222589+0800 pthread[5018:211394] Remaining votes:48Window: ticket window2
2020- 11- 19 15:55:53.426619+0800 pthread[5018:211394] Remaining votes:46Window: ticket window2
2020- 11- 19 15:55:53.426626+0800 pthread[5018:211393] Remaining votes:47Window: ticket window1
2020- 11- 19 15:55:53.630102+0800 pthread[5018:211394] Remaining votes:45Window: ticket window2
2020- 11- 19 15:55:53.630144+0800 pthread[5018:211393] Remaining votes:44Window: ticket window1
2020- 11- 19 15:55:53.832564+0800 pthread[5018:211393] Remaining votes:43Window: ticket window1
2020- 11- 19 15:55:53.832649+0800 pthread[5018:211394] Remaining votes:42Window: ticket window2
2020- 11- 19 15:55:54.033279+0800 pthread[5018:211393] Remaining votes:41Window: ticket window1
2020- 11- 19 15:55:54.033360+0800 pthread[5018:211394] Remaining votes:40Window: ticket window2
2020- 11- 19 15:55:54.237370+0800 pthread[5018:211393] Remaining votes:39Window: ticket window1
2020- 11- 19 15:55:54.237370+0800 pthread[5018:211394] Remaining votes:39Window: ticket window2
2020- 11- 19 15:55:54.440124+0800 pthread[5018:211393] Remaining votes:38Window: ticket window1
2020- 11- 19 15:55:54.440200+0800 pthread[5018:211394] Remaining votes:37Window: ticket window2
2020- 11- 19 15:55:54.643881+0800 pthread[5018:211393] Remaining votes:35Window: ticket window1
2020- 11- 19 15:55:54.643889+0800 pthread[5018:211394] Remaining votes:36Window: ticket window2
2020- 11- 19 15:55:54.845543+0800 pthread[5018:211393] Remaining votes:33Window: ticket window1.Copy the code
This is the result of multiple threads operating on the same critical area at the same time, and the number of votes is distorted, which is not in line with our expectations.
The thread-safe solution is thread synchronization. A common use is to use locks. When one thread occupies a critical section, no other thread is allowed to enter.
IOS implements thread locking in a number of ways. @synchronized, NSLock, NSRecursiveLock, NSCondition, NSConditionLock, pthread_mutex, dispatch_semaphore, OSSpinLock, atomic, etc. For more details on locking, see iOS Multithreaded Programming (7)- Locking. Here we use @synchronized to optimize this case for thread safety.
- (void)initTicketStatusNotSave {
// 1. Set the remaining train ticket to 50
self.ticketSurplusCount = 50;
// 2. Simulate window 1 ticket thread
self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil];
self.ticketSaleWindow1.name = @" Ticket window 1";
// 3. Simulate window 2 ticket thread
self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil];
self.ticketSaleWindow2.name = @" Ticket Window 2";
// 4. Start selling train tickets
[self.ticketSaleWindow1 start];
[self.ticketSaleWindow2 start];
}
/** * sell train tickets (thread safety) */
- (void)saleTicketNotSafe {
while (1) {
/ / the mutex
@synchronized (self) {
// If tickets are available, go on sale
if (self.ticketSurplusCount > 0) {
self.ticketSurplusCount --;
NSLog(@ "% @"[NSString stringWithFormat:@" Remaining votes: %ld window: %@".self.ticketSurplusCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}
// If the ticket is sold out, close the ticket window
else {
NSLog(@" All train tickets sold out");
break; }}}}Copy the code
After running, the result is normal, so I won’t paste it here.