iOS多线程:『pthread、NSThread』详尽总结
2019獨角獸企業重金招聘Python工程師標準>>>
本文用來介紹 iOS 多線程中,pthread、NSThread?的使用方法及實現。
第一部分:pthread 的使用、其他相關方法。
第二部分:NSThread 的使用、線程相關用法、線程狀態控制方法、線程之間的通信、線程安全和線程同步,以及線程的狀態轉換相關知識。
文中 Demo 我已放在了 Github 上,Demo 鏈接:傳送門
1. pthread
1.1 pthread 簡介
pthread 是一套通用的多線程的 API,可以在Unix / Linux / Windows 等系統跨平臺使用,使用 C 語言編寫,需要程序員自己管理線程的生命周期,使用難度較大,我們在 iOS 開發中幾乎不使用 pthread,但是還是來可以了解一下的。
引自?百度百科
POSIX 線程(POSIX threads),簡稱 Pthreads,是線程的 POSIX 標準。該標準定義了創建和操縱線程的一整套 API。在類Unix操作系統(Unix、Linux、Mac OS X等)中,都使用 Pthreads 作為操作系統的線程。Windows 操作系統也有其移植版 pthreads-win32。
引自?維基百科
POSIX 線程(英語:POSIX Threads,常被縮寫 為 Pthreads)是 POSIX 的線程標準,定義了創建和操縱線程的一套 API。
實現 POSIX 線程標準的庫常被稱作 Pthreads,一般用于 Unix-like POSIX 系統,如 Linux、Solaris。但是 Microsoft Windows 上的實現也存在,例如直接使用 Windows API 實現的第三方庫 pthreads-w32;而利用 Windows 的 SFU/SUA 子系統,則可以使用微軟提供的一部分原生 POSIX API。
1.2 pthread 使用方法
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | // 1. 創建線程: 定義一個pthread_t類型變量 pthread_t thread; // 2. 開啟線程: 執行任務 pthread_create(&thread, NULL, run, NULL); // 3. 設置子線程的狀態設置為 detached,該線程運行結束后會自動釋放所有資源 pthread_detach(thread);void * run(void *param) // 新線程調用方法,里邊為需要執行的任務 { NSLog(@"%@", [NSThread currentThread]);return NULL; } |
- pthread_create(&thread, NULL, run, NULL);?中各項參數含義:
- 第一個參數&thread是線程對象,指向線程標識符的指針
- 第二個是線程屬性,可賦值NULL
- 第三個run表示指向函數的指針(run對應函數里是需要在新線程中執行的任務)
- 第四個是運行函數的參數,可賦值NULL
1.3 pthread 其他相關方法
- pthread_create()?創建一個線程
- pthread_exit()?終止當前線程
- pthread_cancel()?中斷另外一個線程的運行
- pthread_join()?阻塞當前的線程,直到另外一個線程運行結束
- pthread_attr_init()?初始化線程的屬性
- pthread_attr_setdetachstate()?設置脫離狀態的屬性(決定這個線程在終止時是否可以被結合)
- pthread_attr_getdetachstate()?獲取脫離狀態的屬性
- pthread_attr_destroy()?刪除線程的屬性
- pthread_kill()?向線程發送一個信號
2. NSThread
NSThread 是蘋果官方提供的,使用起來比 pthread 更加面向對象,簡單易用,可以直接操作線程對象。不過也需要需要程序員自己管理線程的生命周期(主要是創建),我們在開發的過程中偶爾使用 NSThread。比如我們會經常調用[NSThread currentThread]來顯示當前的進程信息。
下邊我們說說 NSThread 如何使用。
2.1 創建、啟動線程
- 先創建線程,再啟動線程
| 1 2 3 4 5 6 7 8 9 | // 1. 創建線程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; // 2. 啟動線程 [thread start]; // 線程一啟動,就會在線程thread中執行self的run方法// 新線程調用方法,里邊為需要執行的任務 - (void)run { NSLog(@"%@", [NSThread currentThread]); } |
- 創建線程后自動啟動線程
| 1 2 3 4 5 6 7 | // 1. 創建線程后自動啟動線程 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];// 新線程調用方法,里邊為需要執行的任務 - (void)run { NSLog(@"%@", [NSThread currentThread]); } |
- 隱式創建并啟動線程
| 1 2 3 4 5 6 7 | // 1. 隱式創建并啟動線程 [self performSelectorInBackground:@selector(run) withObject:nil];// 新線程調用方法,里邊為需要執行的任務 - (void)run { NSLog(@"%@", [NSThread currentThread]); } |
2.2 線程相關用法
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 獲得主線程 + (NSThread *)mainThread;// 判斷是否為主線程(對象方法) - (BOOL)isMainThread;// 判斷是否為主線程(類方法) + (BOOL)isMainThread;// 獲得當前線程 NSThread *current = [NSThread currentThread];// 線程的名字——setter方法 - (void)setName:(NSString *)n;// 線程的名字——getter方法 - (NSString *)name; |
2.3 線程狀態控制方法
- 啟動線程方法
| 1 2 | - (void)start; // 線程進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態 |
- 阻塞(暫停)線!程方法
| 1 2 3 | + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 線程進入阻塞狀態 |
- 強制停止線程
| 1 2 | + (void)exit; // 線程進入死亡狀態 |
2.4 線程之間的通信
在開發中,我們經常會在子線程進行耗時操作,操作結束后再回到主線程去刷新 UI。這就涉及到了子線程和主線程之間的通信。我們先來了解一下官方關于 NSThread 的線程間通信的方法。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | // 在主線程上執行操作 - (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// 在指定線程上執行操作 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0); - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);// 在當前線程上執行操作,調用 NSObject 的 performSelector:相關方法 - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; |
?
下面通過一個經典的下載圖片 DEMO 來展示線程之間的通信。具體步驟如下:
DEMO 代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /** * 創建一個線程下載圖片 */ - (void)downloadImageOnSubThread { // 在創建的子線程中調用downloadImage下載圖片 [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil]; }/** * 下載圖片,下載完之后回到主線程進行 UI 刷新 */ - (void)downloadImage { NSLog(@"current thread -- %@", [NSThread currentThread]);// 1. 獲取圖片 imageUrl NSURL *imageUrl = [NSURL URLWithString:@"https://ysc-demo-1254961422.file.myqcloud.com/YSC-phread-NSThread-demo-icon.jpg"];// 2. 從 imageUrl 中讀取數據(下載圖片) -- 耗時操作 NSData *imageData = [NSData dataWithContentsOfURL:imageUrl]; // 通過二進制 data 創建 image UIImage *image = [UIImage imageWithData:imageData];// 3. 回到主線程進行圖片賦值和界面刷新 [self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:YES]; }/** * 回到主線程進行圖片賦值和界面刷新 */ - (void)refreshOnMainThread:(UIImage *)image { NSLog(@"current thread -- %@", [NSThread currentThread]);// 賦值圖片到imageview self.imageView.image = image; } |
?
2.5 NSThread 線程安全和線程同步
線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步:可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,于是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。
舉個簡單例子就是:兩個人在一起聊天。兩個人不能同時說話,避免聽不清(操作沖突)。等一個人說完(一個線程結束操作),另一個再說(另一個線程再開始操作)。
下面,我們模擬火車票售賣的方式,實現 NSThread 線程安全和解決線程同步問題。
場景:總共有50張火車票,有兩個售賣火車票的窗口,一個是北京火車票售賣窗口,另一個是上海火車票售賣窗口。兩個窗口同時售賣火車票,賣完為止。
2.5.1 NSThread 非線程安全
先來看看不考慮線程安全的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /** * 初始化火車票數量、賣票窗口(非線程安全)、并開始賣票 */ - (void)initTicketStatusNotSave { // 1. 設置剩余火車票為 50 self.ticketSurplusCount = 50;// 2. 設置北京火車票售賣窗口的線程 self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil]; self.ticketSaleWindow1.name = @"北京火車票售票窗口";// 3. 設置上海火車票售賣窗口的線程 self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil]; self.ticketSaleWindow2.name = @"上海火車票售票窗口";// 4. 開始售賣火車票 [self.ticketSaleWindow1 start]; [self.ticketSaleWindow2 start];}/** * 售賣火車票(非線程安全) */ - (void)saleTicketNotSafe { while (1) { //如果還有票,繼續售賣 if (self.ticketSurplusCount > 0) { self.ticketSurplusCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已賣完,關閉售票窗口 else { NSLog(@"所有火車票均已售完"); break; } } } |
運行后部分結果為:
可以看到在不考慮線程安全的情況下,得到票數是錯亂的,這樣顯然不符合我們的需求,所以我們需要考慮線程安全問題。
2.5.2 NSThread 線程安全
線程安全解決方案:可以給線程加鎖,在一個線程執行該操作的時候,不允許其他線程進行操作。iOS 實現線程加鎖有很多種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各種方式。為了簡單起見,這里不對各種鎖的解決方案和性能做分析,只用最簡單的@synchronized來保證線程安全,從而解決線程同步問題。
考慮線程安全的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /** * 初始化火車票數量、賣票窗口(線程安全)、并開始賣票 */ - (void)initTicketStatusSave { // 1. 設置剩余火車票為 50 self.ticketSurplusCount = 50;// 2. 設置北京火車票售賣窗口的線程 self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil]; self.ticketSaleWindow1.name = @"北京火車票售票窗口";// 3. 設置上海火車票售賣窗口的線程 self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil]; self.ticketSaleWindow2.name = @"上海火車票售票窗口";// 4. 開始售賣火車票 [self.ticketSaleWindow1 start]; [self.ticketSaleWindow2 start];}/** * 售賣火車票(線程安全) */ - (void)saleTicketSafe { while (1) { // 互斥鎖 @synchronized (self) { //如果還有票,繼續售賣 if (self.ticketSurplusCount > 0) { self.ticketSurplusCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已賣完,關閉售票窗口 else { NSLog(@"所有火車票均已售完"); break; } } } } |
運行后結果為:
省略一部分結果圖。。。
可以看出,在考慮了線程安全的情況下,加鎖之后,得到的票數是正確的,沒有出現混亂的情況。我們也就解決了多個線程同步的問題。
2.6 線程的狀態轉換
當我們新建一條線程NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];,在內存中的表現為:
當調用[thread start];后,系統把線程對象放入可調度線程池中,線程對象進入就緒狀態,如下圖所示。
當然,可調度線程池中,會有其他的線程對象,如下圖所示。在這里我們只關心左邊的線程對象。
下邊我們來看看當前線程的狀態轉換。
- 如果CPU現在調度當前線程對象,則當前線程對象進入運行狀態,如果CPU調度其他線程對象,則當前線程對象回到就緒狀態。
- 如果CPU在運行當前線程對象的時候調用了sleep方法\等待同步鎖,則當前線程對象就進入了阻塞狀態,等到sleep到時\得到同步鎖,則回到就緒狀態。
- 如果CPU在運行當前線程對象的時候線程任務執行完畢\異常強制退出,則當前線程對象進入死亡狀態。
只看文字可能不太好理解,具體當前線程對象的狀態變化如下圖所示。
轉載于:https://my.oschina.net/u/3697347/blog/1648478
總結
以上是生活随笔為你收集整理的iOS多线程:『pthread、NSThread』详尽总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Navicat for MySQL出现1
- 下一篇: 更改分区目录图片