Ios

iOSマルチスレッド-知っておく必要のあるNSThreadはこちら



Ios Multithreading Nsthread You Need Know Is Here

ソースを示してください http://blog.csdn.net/u014205968/article/details/78323156

この一連の記事では、主にiOSでのマルチスレッドの使用について説明しています。これには、NSThread、GCD、NSOperation、およびRunLoopの詳細な説明が含まれます。この一連の記事には、基本的なスレッド/プロセス、同期/非同期、ブロッキング/非ブロッキング、シリアル/パラレルは含まれていません。これらの基本的な概念を理解していない読者も参照してください。このシリーズの記事は、説明のために次の記事に分けられます。読者は必要に応じて相談することができます。



組織構造の説明

この一連の記事は、関連するマルチスレッドクラスの抽象レベル、つまりNSThreadはいFoundationフレームワークによって提供される最も基本的なマルチスレッドクラス、それぞれ| _ + _に従って書かれています。 |クラスのオブジェクトはスレッドを表し、Appleはそれを開発者向けにカプセル化しますNSThreadGCD(Grand Central Dispatch)比較GCDつまり、便利な操作方法を提供します。開発者は、スレッドのライフサイクルの管理に集中する必要がなくなり、スレッドを再利用するためにスレッドプールを自分で管理する必要もなくなりましたが、NSThread So GCD外部インターフェイスを提供するので、C functionフレームインFoundationオブジェクト指向のカプセル化に基づいて、オブジェクト指向のマルチスレッドクラスが提供されますGCD with NSOperation、抽象化レベルが高くなります。

NSOperationQueueはいOCのスーパーセットにより、開発者は使用することもできますC language標準スレッドPOSIXpthreadpthreadすべてkernel NSThread of mach kernelパッケージなので、通常、開発中は使用されませんmach thread



pthreadこれはスレッドに関連する基本的なコンポーネントです。タスクの実行後にスレッドが終了せず、CPUリソースを節約するタスクがない場合はスリープする必要があります。RunLoopしたがって、スレッドを正しく理解するには、関連情報を深く理解する必要がありますRunLoop

NSThreadの姿勢に対する完全な解決策

in RunLoop言及、Organizational structure descriptionカーネルへNSThread中間mach kernelパッケージング、つまりすべてmach threadオブジェクトは実際にはスレッドなので、| _を作成します+ _ | Objectは、新しいスレッドを作成したことも意味します。初期作成NSThreadいくつかの方法があります。

NSThread

ここにいくつかの栗があります:



NSThread

上記の栗は実用的な意味はありません。スレッドを作成して開始する方法を示すためだけに、プログラムを開始した後、プログラムの出力を10回確認できます。

/* Use the selector of the target object as the task execution body of the thread. The selector method can receive at most one parameter, which is the argument */ - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) /* Use block as the task execution body of the thread */ - (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) /* Class method, the return value is void Use a block as the execution body of the thread and start the thread directly The above instance method returns the NSThread object, you need to manually call the start method to start the thread to perform tasks */ + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) /* Class method, the return value is void Use the selector of the target object as the task execution body of the thread. The selector method receives at most one parameter, which is the argument Similarly, this method will automatically start the thread after the county is created, without manual triggering */ + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument

スレッドの名前が上に出力され、渡したパラメーターも出力されます。新しいスレッドを作成して、非常に単純なコードでタスクを実行できます。開発では、時間のかかる操作を他のスレッドに入れて実行するようにしてください。UIの更新操作のみがメインスレッドで実行されます。

一般に、上記のメソッドで作成されたスレッドは、タスク実行本体の実行後に終了して破棄されます。/* Note: The chestnuts in this article are all executed in a single-view project to prevent other threads from exiting after the main thread exits, which is not convenient for experimentation. */ //The task execution body of the thread and receives a parameter arg - (void)firstThread:(id)arg { for (int i = 0 i <10 i++) { NSLog(@'Task %@ %@', [NSThread currentThread], arg) } NSLog(@'Thread Task Complete') } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear: YES] /* Create a thread, the thread task execution body is the firstThread: method This method can receive parameters @'Hello, World' */ NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(firstThread:) object:@'Hello, World'] //Set the name of the thread for easy viewing [thread setName:@'firstThread'] //Start thread [thread start] } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear: YES] NSLog('ViewDidAppear') }メソッド2番目Task 0x1c446f780>{number = 4, name = firstThread} Hello, WorldメソッドおよびfirstThread:メソッドの出力のブレークポイント、および次に、プログラムを実行して、最初のブレークポイントでスレッド情報を表示しますNSLogメソッドのブレークポイントでは、プログラムのスレッド情報は次のとおりです。

画像

上の図からわかるように、viewDidAppear:という名前のスレッドがあります。このスレッドは私たちのために作成されますfirstThread:オブジェクトfirstThreadはメインスレッドの名前です。ここでNSThreadメインスレッドがシリアルであることを示します。このコンテンツは次のようになりますcom.apple.main-thread(serial)説明のために、クラスメソッドを使用できますserialメインスレッドを取得します。次に、2番目のブレークポイントまで実行を続行します。プログラムのスレッド情報は、次のとおりです。

画像

上の図からわかるように、GCDスレッドは終了しました。これは、タスク実行本体の実行後にスレッドが終了して破棄されるためです。

この栗は、再利用できないことも示しています[NSThread mainThread]スレッドの作成はプロセスよりも軽量ですが、スレッドの作成は通常のオブジェクトの作成よりも多くのリソースを消費し、メインスレッドとイベント処理を受信するスレッドは引き続き存在する、まさにfirstThread役割、このコンテンツもNSThread説明の一部になります。

次に、作成の説明を続けますRunLoopその他の方法は次のとおりです。

RunLoop

上記のすべてNSThread作成メソッドについてもう一度説明します。インスタンスメソッドとクラスメソッドの違いは、インスタンスメソッドが//Chestnut 2: /* Create a thread by passing in a block, and the thread execution body is the content of the block But the thread created in this way cannot pass in parameters */ NSThread *thread = [[NSThread alloc] initWithBlock:^{ for (int i = 0 i <100 i++) { NSLog(@'Task %@', [NSThread currentThread]) } }] //Set the thread name [thread setName:@'firstThread'] //Start thread [thread start] //Chestnut 3: /* Create and automatically start a thread through a class method The execution body of the thread is the incoming block */ [NSThread detachNewThreadWithBlock:^{ for (int i = 0 i <100 i++) { NSLog(@'Task %@', [NSThread currentThread]) } }] //Chestnut 4: /* Create and automatically start a thread through a class method The execution body of this thread is the firstThread: method of self, and the relevant parameters are passed in */ [NSThread detachNewThreadSelector:@selector(firstThread:) toTarget:self withObject:@'Hello, World!'] Objectを返すことです。これは、スレッドを開始する必要があるときに手動でトリガーする必要があります。 NSThreadメソッドであり、クラスメソッドに戻り値がない場合、スレッドはスレッドが作成された直後に開始されます。スレッドの開始NSThreadこの方法は、スレッドの状態を新しく作成されたものから準備完了に変更するだけであり、システムはスレッドのタスクが実行されるタイミングをスケジュールする必要があります。

次を見てくださいstartより一般的に使用される属性とメソッドのいくつか:

start

次に、別の栗を与えます:

NSThread

上記の栗も比較的シンプルです。ビューにボタンが追加されます。ボタンをクリックすると、作成したスレッドでexitメソッドが実行されます。/* Class attribute, used to get the current thread If it is called from the main thread, return the main thread object If called in other threads, return to other current threads What thread is called, what thread is returned */ @property (class, readonly, strong) NSThread *currentThread //Class attribute, used to return to the main thread, no matter what thread the call returns to the main thread @property (class, readonly, strong) NSThread *mainThread /* Set the priority of the thread, the range is 0-1 doule type, the larger the number, the higher the priority We know that when the system is performing thread scheduling, the higher the priority, the greater the possibility of being selected to the execution state But we can't just rely on priority to judge the execution order of multithreading, the execution order of multithreading cannot be predicted */ @property double threadPriority //The name of the thread, the chestnut in front has already been introduced @property (nullable, copy) NSString *name //Determine whether the thread is executing @property (readonly, getter=isExecuting) BOOL executing //Determine whether the thread is over @property (readonly, getter=isFinished) BOOL finished //Determine whether the thread is cancelled @property (readonly, getter=isCancelled) BOOL cancelled /* Let the thread sleep, immediately give up the current time slice, give up CPU resources, and enter the blocking state Class method, what thread executes the method, what thread will sleep */ + (void)sleepUntilDate:(NSDate *)date //Same as above, pass in the time here + (void)sleepForTimeInterval:(NSTimeInterval)ti //Exit the current thread, which thread executes, and which thread exits + (void)exit /* Instance method, cancel thread Calling this method will set the cancelled property to YES, but does not exit the thread */ - (void)cancelスレッドが作成され、メソッドで開始されます。このスレッドは、ループするたびに現在のスレッドがキャンセルされるかどうかを決定します。キャンセルされると、現在のスレッドを終了します。その後、スレッドは破棄されます。各ループの実行後、現在のスレッドは1秒間スリープします。 、多くの人がここで誤解しているかもしれません。スレッドをスリープ状態にすると、スレッドはブロッキング状態になります。スリープ時間が経過すると、ブロッキング状態からレディ状態になります。システムスレッドによって実行状態にスケジュールされた後にのみ、実行を継続できます。 1秒後に実行を継続するという意味ではありませんが、1秒後にブロッキング状態からレディ状態になり、後で実行する時期はシステムのスケジューリングによって決まります。説明する必要があるのは、//Button click event handler - (void)btnClicked { //Cancel thread [self.thread cancel] } - (void)viewWillAppear:(BOOL)animated { self.thread = [[NSThread alloc] initWithBlock:^{ for (int i = 0 i <100 i++) { //Get the currently executing thread, that is, self.thread NSThread *currentThread = [NSThread currentThread] //Determine whether the thread is cancelled if ([currentThread isCancelled]) { //If it is cancelled, exit the currently executing thread, that is, self.thread [NSThread exit] } NSLog(@'Task %@', currentThread) //In the loop, sleep for 1s each time [NSThread sleepForTimeInterval:1] } }] [self.thread setName:@'firstThread'] //Start thread [self.thread start] }メソッドによってスレッドが終了するのではなく、viewWillAppear:属性cancel、手動で終了cancelledメソッドをトリガーする必要があることです。

したがって、上記のコードを実行した後、1秒に1回出力されます。ボタンをクリックすると、スレッドはYES属性exit、次にスレッドが実行されるときに実行されますcancelledメソッドはスレッドを終了します。スレッドを終了すると、現在実行中のタスクがすぐに終了します。つまり、YESメソッドの後のコードは実行されなくなります。

スレッドを終了するには、次の3つの状況があります。

  • タスクエグゼクティブは、実行が完了した後、正常に終了します
  • タスク実行本体の実行中の例外も、現在のスレッドを終了させます
  • NSThreadクラスのexitメソッドを実行して、現在のスレッドを終了します

優先順位については詳しく説明しません。自分で実験することができます。たとえば、2つのスレッドを開始し、forループを使用してテキストを出力し、異なる優先順位を設定します。優先度の高いスレッドがタイムスライスを取得できることがわかります。出力を実行する可能性は、優先度の低いものよりも高くなります。

次に、複数のスレッドから写真をダウンロードする簡単な栗を与えます。

exit

上記で使用exit提供- (void)viewWillAppear:(BOOL)animated { //Create a thread to download pictures NSThread *thread = [[NSThread alloc] initWithBlock:^{ UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1508398116220&di=ba2b7c9bf32d0ecef49de4fb19741edb&imgtype=0&src=http%3A%2F%2Fwscont2.apps.microsoft.com%2Fwinstore%2F1x%2Fea9a3c59-bb26-4086-b823-4a4869ffd9f2%2FScreenshot.398115.100000.jpg']]] //After the picture is downloaded, use the main thread to perform the operation of updating the UI [self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:NO] }] //Start thread [thread start] } //The main thread executes the current method of updating the UI - (void)updateImage:(UIImage*)image { self.imageView.image = image }メソッド、このメソッドはメインスレッドを使用して関連するメソッドを実行するために使用されますNSObject UIの更新操作には規制があり、実行する必要がありますメインスレッド。それ以外の場合は、ランタイム警告が生成されます。最も重要なことは、メインスレッドが実行されない限り更新操作が実行されないことです。これにより、さまざまな事故が発生する可能性があります。

NSThreadロックメカニズムの古典的な生産者/消費者問題

マルチスレッドについて言及すると、必然的に考慮されますperformSelectorOnMainThread:WithObject:watiUntilDone:iOS同期メカニズムとロックメカニズムも提供します。次に、古典的な銀行の引き出し栗を見てみましょう:

Race conditions

上記の栗は非常に単純で、a OCを定義します。クラスは銀行口座を表し、//Define an Account class @interface Account: NSObject //account number @property (nonatomic, strong) NSString *accountNumber //Balance @property (nonatomic, assign) double balance //Withdraw money operation - (void)draw:(id)money @end @implementation Account @synthesize accountNumber = _accountNumber @synthesize balance = _balance - (void)draw:(id)money { double drawMoney = [money doubleValue] //Judge whether the balance is enough if (self.balance >= drawMoney) { //The current thread sleeps for 1 millisecond //[NSThread sleepForTimeInterval:0.001] self.balance -= drawMoney NSLog(@'%@ draw money %lf balance left %lf', [[NSThread currentThread] name], drawMoney, self.balance) } else { //Insufficient balance, prompt NSLog(@'%@ Balance Not Enouth', [[NSThread currentThread] name]) } } @end //ViewController.m - (void)viewWillAppear:(BOOL)animated { Account *account = [[Account alloc] init] account.accountNumber = @'1603121434' account.balance = 1500.0 NSThread *thread1 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)] [thread1 setName:@'Thread1'] NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)] [thread2 setName:@'Thread2'] [thread1 start] [thread2 start] }メソッドでは、注釈付きAccountコード、次にお金を引き出す操作を定義します。お金を引き出すためにビューに2つのスレッドを作成しました。上記のプログラムを実行すると、スレッド1がお金を引き出し、スレッド2が残高が不足していることを示すプロンプトが表示されますが、この結果は必ずしも正しいとは限りません。マルチスレッドの実行順序は予測できないと述べました。スレッド2の優先度がスレッド1よりも低い場合でも、スレッド2が最初に実行される可能性があるため、コメント行からコメントを削除して、撤回の判断条件本体に入る最初のスレッドをシミュレートします。システムスレッドスケジューリングスイッチ。この時点での出力結果は次のとおりです。

draw:

これは[NSThread sleepForTimeInterval:0.001]、繰り返しませんThread1 draw money 1000.000000 balance left 500.000000 Thread2 draw money 1000.000000 balance left -500.000000スレッド1が判定本体に入った後、引き出し操作が実行される前に準備完了状態に切り替わります。システムはスレッド2の実行を切り替えます。スレッド1はまだ引き出し操作を実行していないため、残高は要件を満たし、スレッド2も判定機関に入り、両方のスレッドがお金を引き出すことができます。

解決Race conditionsロックメカニズムや同期コードブロックなど、多くの方法があります。次に、2つの栗をあげます。

Race conditions

チェスナット2では正しいですRace conditionsメソッドは同期コードブロックを追加します//Chestnut 2: - (void)draw:(id)money { @synchronized (self) { double drawMoney = [money doubleValue] if (self.balance >= drawMoney) { [NSThread sleepForTimeInterval:0.001] self.balance -= drawMoney NSLog(@'%@ draw money %lf balance left %lf', [[NSThread currentThread] name], drawMoney, self.balance) } else { NSLog(@'%@ Balance Not Enouth', [[NSThread currentThread] name]) } } } //Chestnut 3: - (void)draw:(id)money { /* self.lock is initialized in the initialization function of ViewController self.lock = [[NSLock alloc] init] */ [self.lock lock] double drawMoney = [money doubleValue] if (self.balance >= drawMoney) { [NSThread sleepForTimeInterval:0.001] self.balance -= drawMoney NSLog(@'%@ draw money %lf balance left %lf', [[NSThread currentThread] name], drawMoney, self.balance) } else { NSLog(@'%@ Balance Not Enouth', [[NSThread currentThread] name]) } [self.lock unlock] }囲まれたコードは同期コードブロックであり、同期コードブロックにはリスナーが必要ですdraw:を使用しますオブジェクト自体は@synchronizedオブジェクト生成accountであるため、リスナーとして機能します。同期コードブロックを実行するときは、最初にリスナーを取得する必要があります。取得されない場合、スレッドはブロックされます。同期コードブロックが実行されると、リスナーが解放され、account of Race conditions同期コードブロックと同じになります。

栗3、ロックメカニズムを使用してクラスのjavaロックオブジェクトを作成しますsynchronizedこのメソッドはロックを取得するために使用されます。ロックが他のオブジェクトによって占有されている場合、スレッドはブロックされます。NSLockこのメソッドは、他のスレッドがロックできるようにロックを解放するために使用されます。

スレッドのスケジューリングは開発者には透過的です。スレッドの実行順序を予測することはできませんが、特定の条件に従ってスレッドを実行する必要があり、スレッド間で通信する必要がある場合があります。lockスレッド間の通信方法を提供します。チェックしてくださいunlock宣言ファイル:

NSCondition

NSCondition達成NS_CLASS_AVAILABLE(10_5, 2_0) @interface NSCondition : NSObject <NSLocking> { @private void *_priv } /* The thread calling the wait method of the NSCondition object will block until other threads call the signal method or broadcast method of the object to wake up After waking up, the thread changes from the blocked state to the ready state, and is handed over to the system for thread scheduling When the wait method is executed, the unlock method is automatically executed to release the lock and block the thread */ - (void)wait //Same as above, but this method wakes up the thread when the limit arrives - (BOOL)waitUntilDate:(NSDate *)limit /* Wake up a thread blocked on the current NSCondition object If there are multiple threads waiting on the object, one is randomly selected, and the selected thread enters the ready state from the blocked state */ - (void)signal /* Same as above, this method will wake up all threads blocked on the current NSCondition object */ - (void)broadcast @property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) @end NS_ASSUME_NONNULL_END合意、つまりNSConditionロック機能もあり、NSLocking同じものを使用してロックを取得および解放できます。理解したNSCondition基本的な方法は生産者/消費者問題を実現することができます:

NSLock

上記の栗も比較的単純です。NSCondition注意が必要なのはその@interface Account: NSObject @property (nonatomic, strong) NSString *accountNumber @property (nonatomic, assign) double balance @property (nonatomic, strong) NSCondition *condition @property (nonatomic, assign) BOOL haveMoney - (void)deposite:(id)money - (void)draw:(id)money @end @implementation Account @synthesize accountNumber = _accountNumber @synthesize balance = _balance @synthesize condition = _condition @synthesize haveMoney = _haveMoney //NSCondition getter, used to create NSCondition object - (NSCondition*)condition { if (_condition == nil) { _condition = [[NSCondition alloc] init] } return _condition } - (void)draw:(id)money { //Set the consumer to withdraw 20 times int count = 0 while (count <20) { //First use condition to lock, block if other threads are already locked [self.condition lock] //Judging whether there is money if (self.haveMoney) { //If you have money, you can withdraw money and set haveMoney to NO self.balance -= [money doubleValue] self.haveMoney = NO count += 1 NSLog(@'%@ draw money %lf %lf', [[NSThread currentThread] name], [money doubleValue], self.balance) //After the withdrawal operation is completed, wake up other threads waiting on the second condition [self.condition broadcast] } else { //If there is no money, wait on the second condition and block [self.condition wait] //If the blocked thread is awakened, it will continue to execute the code NSLog(@'%@ wake up', [[NSThread currentThread] name]) } //Release the lock [self.condition unlock] } } - (void)deposite:(id)money { //Create three withdrawal threads, each withdraw money 20 times, then deposit money 60 times int count = 0 while (count <60) { //Locked, if other threads are locked, block [self.condition lock] //Judge if there is no money, then proceed to deposit money if (!self.haveMoney) { //Perform deposit operation and set haveMoney to YES self.balance += [money doubleValue] self.haveMoney = YES count += 1 NSLog(@'Deposite money %lf %lf', [money doubleValue], self.balance) //Wake up all other threads waiting on the condition [self.condition broadcast] } else { //If you have money, wait [self.condition wait] NSLog(@'Deposite Thread wake up') } //Release the lock [self.condition unlock] } } @end - (void)viewWillAppear:(BOOL)animate { [super viewWillAppear:YES] Account *account = [[Account alloc] init] account.accountNumber = @'1603121434' account.balance = 0 //Consumer thread 1, take 1000 yuan each time NSThread *thread = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)] [thread setName:@'consumer1'] //Consumer thread 2, each take 1000 yuan NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)] [thread2 setName:@'consumer2'] //Consumer thread 3, each take 1000 yuan NSThread *thread3 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)] [thread3 setName:@'consumer3'] // Producer thread, each time deposit 1000 yuan NSThread *thread4 = [[NSThread alloc] initWithTarget:account selector:@selector(deposite:) object:@(1000)] [thread4 setName:@'productor'] [thread start] [thread2 start] [thread3 start] [thread4 start] }実行中のメソッドNSConditionメソッドの前に、もちろんロジックに従って、ロックを取得する必要があります最初に回避するwait、実行waitこのメソッドは、他のスレッドがこれを呼び出すまで現在のスレッドをブロックしますRace conditionsブロックされたスレッドをウェイクアップするために、ブロックされたスレッドは、目覚め、スケジュールされ実行された後、ロックを再取得しますwaitメソッドの次のコード行は実行を継続します。もう1つ注意しなければならないのは、お金があるかどうかですconditionこのフラグ、このフラグの意味は、スレッドが起動して準備完了状態に入ると、開発者はシステムスレッドをスケジュールするスレッドがわからないということです。次のスケジュールを設定します。つまり、次の実行がプロデューサーであるとは予測できません。それでも消費者は、間違いを避けるために、判断のためのフラグを追加します。

上記のコードは、Appleの公式ドキュメントの順序で記述されています。詳細については、wait公式ドキュメントを参照してください。 Apple NSCondition

備考

作者のレベルが限られているため、エラーが発生することは避けられません。