多線程的基本概念的理解

模擬耗時操作

  • 耗時操作對UI的影響 : 會卡死UI / 界面 / 主線程
  • 如何解決耗時操作卡死主線程?
  • 使用多線程技術,把耗時的操作放進子線程中執行,使得主線程有資源處理UI
  • 多線程的核心思想 : 把耗時操作放到子線程異步執行

多線程基本概念

同步和異步

  • 同步和異步是任務 / 代碼 執行的兩種方式

同步

  • 多個任務按順序依次執行,就是同步執行

異步

  • 多個任務同時執行,就是異步執行
  • 如何保證多個任務同時執行?
  • 開線程,開多個線程,就可以保證多個任務同時執行
  • 提示 : 凡是遇到異步 / 多線程 / 耗時操作 第一反應就是需要開啟新的子線程
  • 學習多線程就是為了如何讓任務在子線程異步執行

進程和線程

進程

  • 系統中正在運行的應用程序叫做進程
  • 進程可以類必成公司

線程 / 多線程

  • 程序一啟動就會默認開啟一個線程,稱之為主線程
  • 線程是進程最基本的執行單元,進程里面所有的任務都在線程中執行的
  • 一個進程,可以開啟多個線程,稱之為多線程

多線程執行原理

  • CPU在多個線程之間,快速來回的切換,調度線程執行任務,如果切換的速度足夠快,就造成多個任務同時執行的假象

多線程優缺點

優點

  • 可以適當提高程序執行的效率 (開啟多個線程下載視頻)

缺點

  • 前提 : 當線程非常多的時候,就暴露缺點
  • 會消耗大量的CPU資源
  • 時間開銷 / 空間開銷
  • 多線程的使用原則 : 能不用就不用,如果非要用,就簡單的使用,少用

主線程

  • 作用 : 刷新UI / 處理UI事件
  • 使用時的注意點 : 不要把耗時操作放到主線程中執行

pthread

  • 學習pthread的目的 : 就是為了復習C語言相關的知識點
  • 在C語言中,一般帶_t / _ref標識數據類型
  • NULL : 表示空地址,一般在C語言使用;
  • nil : 表示空對象,一般在OC使用;
  • 其實,NULLh和nil本質上沒有半點兒區別
  • void *(*)(void *) : 表示指向函數的指針,即函數名;函數名就是表示函數地址;
  • 數組地址就是數組名或者數組第0個角標元素的地址
  • void * 表示可以指向任何地址的指針,代表任意數據類型;類似于OC的id;
返回值    函數名    函數參數
void *    (*)    (void *)

橋接

  • 使用場景 : 在C語言和OC語言混合開發時,需要做數據類型轉換,有時候需要使用橋接;
  • 橋接作用 : 在做數據類型轉換時,告訴編譯器如何管理C語言的內存
  • 提示 : 在ARC環境下,編譯器在編譯時,不會自動管理C語言申請的內存空間
  • 提示: 在ARC環境下,加上__bridge 表示告訴編譯器C語言申請的內存也是自動管理的,因為大環境是ARC的
  • 提問 : MRC環境下,需要使用__bridge 嗎? 不需要,因為本來就是手動管理的

NSThread創建線程三種方式

構造方法

  • 可以拿到線程對象
  • 需要自己啟動線程
// 創建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
// 啟動線程
[thread start];

類方法

  • 不可以拿到線程對象
  • 不需要自己啟動線程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];

NSObject分類方法

  • 不可以拿到線程對象
  • 不需要自己啟動線程
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];

target和selector的關系

  • 執行哪個對象的哪個方法
  • 需求 : 執行Person對象的run方法,run方法需要在子線程執行
// 創建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"person"];
// 啟動線程
[thread start];

線程生命周期 / 線程狀態

  • 新建狀態
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
  • 就緒狀態
[thread start];
  • 運行狀態 : 程序員無法干預
  • 阻塞狀態 : 調用sleep方法 / 添加互斥鎖(同步鎖)
  • 死亡狀態
  • 正常死亡 : 任務執行結束
  • 異常死亡 : exit

線程屬性

  • name : 標識唯一的線程對象,方便定位線程對象
  • threadPriority : 決定了線程有更多的機會被CPU調度執行;等同于qualityOfService;實際開發中千萬不要隨意修改
  • stackSize : 線程對象占用內存空間大小.主線程 / 子線程 512KB

多線程訪問共享資源 (會造成線程安全問題)

  • 當多個線程同時操作共享資源,就會出現線程安全問題
  • 解決辦法 : 加鎖 (互斥鎖 / 同步鎖)
  • 互斥鎖 / 同步鎖 : 使用了線程同步技術
  • 特點 : 可以保證被鎖定的代碼,同一時間只有一個線程可以訪問
  • self : 表示互斥鎖的參數;互斥鎖的參數,又叫做鎖對象;
  • 鎖對象 : 任何繼承自NSObject的對象,都可以作為互斥鎖的參數;內部有把鎖,默認是開啟的
  • 鎖對象必須是全局的對象;self是最方便獲取的全局的鎖對象
  • 局部鎖對象是鎖不住的,因為每次線程進來之前會新建一把鎖
  • 提示 : 加鎖的事情,不是再客戶端操作的;是服務器加鎖的,多線程資源共享絕大多數是在服務器發生;
  • 提示 : 加鎖是犧牲了性能,保證安全.客戶端的性能不能輕易犧牲

異步下載網絡圖片

  • 在 iOS 開發中,使用多線程只有一個目的:將耗時操作放在后臺工作,待工作完成后,通知主線程更新 UI

  • 耗時的下載操作放在子線程

- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self loadImageData];
    
    // 在子線程執行耗時操作
    [self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}
// 下載圖片的主方法
- (void)loadImageData
{
    // URL
    NSURL *URL = [NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1476696781&di=f721c3cba572282b9d4b135866894858&src=http://www.hn.xinhuanet.com/2016-08/31/1119483302_14726048769591n.jpg"];
    // 發送網絡請求,獲取圖片二進制數據,是個耗時操作
    NSData *data = [NSData dataWithContentsOfURL:URL];
    // image : 就是子線程執行的結果,需要傳遞到主線程
    UIImage *image = [UIImage imageWithData:data];
    
    // 下載完成之后,通知主線程刷新UI
    // waitUntilDone : 是否等待updateUI執行完,再執行后面的代碼,一般傳入NO
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:NO];
    
    NSLog(@"后面的代碼");
}
  • 更新UI的操作在主線程
// 回到主線程更新UI
- (void)updateUI:(UIImage *)image
{
    self.imgView.image = image;
    [self.imgView sizeToFit];
    self.scrollView.contentSize = image.size;
}
  • 在子線程下載圖片,在主線程更新UI,是線程間通信的一種;
  • 線程間通信 : 一個線程把他執行的結果,傳遞到另外的一個線程
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容