多線程簡介

多線程簡介

iOS系統(tǒng) 中,每一個應(yīng)用都是一個進程。具體了解Runloop底層原理:http://www.itdecent.cn/p/9cb4edc0670d,除了Runloop底層原理還介紹了線程間的通訊等。

進程與線程

  • 線程是進程的基本執(zhí)行單元,一個進程的所有任務(wù)都在線程中執(zhí)行
  • 進程要想執(zhí)行任務(wù),必須得有線程,進程至少要有一條線程
  • 程序啟動會默認開啟一條線程,這條線程被稱為主線程或 UI 線程
  • 進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,每個進程之間是獨立的,每個進程均運行在其專用的且受保護的內(nèi)存。

進程與線程的關(guān)系

  • 地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
  • 資源擁有:同一進程內(nèi)的線程共享本進程的資源如內(nèi)存、I/O、cpu等,但是進程之間的資源是獨立的。
  • 一個進程崩潰后,在保護模式下不會對其他進程產(chǎn)生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。
  • 進程切換時,消耗的資源大,效率高。所以涉及到頻繁的切換時,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進程
  • 執(zhí)行過程:每個獨立的進程有一個程序運行的入口、順序執(zhí)行序列和程序入口。但是線程不能獨立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
  • 線程是處理器調(diào)度的基本單位,但是進程不是。

多線程的意義

多線程的原理其實就是CPU在單位時間片里快速在各個線程之間切換。一般情況下無論多核還是單核,我們的線程運行總是 "并發(fā)" 的,這時候我們所說的"并發(fā)"是一種模擬出來的狀態(tài),CPU在單位時間片里快速在各個線程之間切換,每個線程執(zhí)行一小段時間,讓多個線程看起來就像在同時運行。只有當(dāng)cpu數(shù)量大于等于線程數(shù)量,這個時候是真正并發(fā),可以多個線程同時執(zhí)行計算。

優(yōu)點
  • 能適當(dāng)提高程序的執(zhí)行效率。
  • 能適當(dāng)提高資源的利用率(CPU、內(nèi)存)。
  • 線程上的任務(wù)執(zhí)行完成后,線程會自動銷毀。
缺點
  • 開啟線程需要占用一定的內(nèi)存空間(默認情況下,每一個線程占用512KB)。
  • 如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能。
  • 線程越多,CPU在調(diào)度線程上的開銷就越大。
  • 程序設(shè)計更加復(fù)雜,比如線程間的通訊、多線程的數(shù)據(jù)共享。

線程的生命周期

線程在創(chuàng)建后,start開始進入Runnable就緒的狀態(tài),這時會執(zhí)行很多初始化的操作;接下進入running狀態(tài),CPU會調(diào)度當(dāng)前線程,如果線程池中有當(dāng)前線程會直接執(zhí)行,在時間片的影響后CPU再次調(diào)度其他線程,直到當(dāng)前線程任務(wù)執(zhí)行完畢或強制退出,或者堵塞(調(diào)用sleep、等待同步鎖或者從可調(diào)度線程池中移除)結(jié)束再次回到Runnable狀態(tài)。

多線程相關(guān)補充

多線程技術(shù)方案
image.png
線程池

線程池可以使線程得到復(fù)用,所謂線程復(fù)用就是線程在執(zhí)行完一個任務(wù)后并不被銷毀,該線程可以繼續(xù)執(zhí)行其他的任務(wù)。在線程池大小小于核心線程池大小的時候,如果小于則直接創(chuàng)建線程執(zhí)行任務(wù)。如果超出核心線程池大小則依賴隊列,此時線程池會判斷當(dāng)前隊列是不是已經(jīng)滿了,如果沒有滿,則提交任務(wù)到工作隊列中,等待線程池調(diào)度執(zhí)行任務(wù)。如果滿了,并且當(dāng)前工作隊列所依賴的線程沒有執(zhí)行工作,那么則可以利用當(dāng)前線程執(zhí)行任務(wù),如果此時線程都在工作,接下來會交給飽和策略。飽和策略一般默認都是中止策略,調(diào)用者可以捕獲到該異常;還有拋棄策略,會悄悄拋棄該任務(wù),不過一般會拋棄最舊的任務(wù)或者優(yōu)先級比較低的任務(wù)等;還有調(diào)用者運行策略,實現(xiàn)了一種機制,這個機制不會拋棄任務(wù)也不會拋出異常,而是將任務(wù)回退到調(diào)用者,來達到降低新任務(wù)的流量。還有等待策略,也就是需要排隊等候執(zhí)行。

線程安全--鎖

在開發(fā)高性能程序的時候幾乎都會用到多線程,但是用到多線程也會碰到一些安全問題。比如多個線程同時對一塊內(nèi)存發(fā)生讀和寫的操作,或者程序執(zhí)行的順序會被打亂,可能造成提前釋放一個變量,造成計算結(jié)果錯誤等,所以我們經(jīng)常會用到鎖。我們用鎖來保證代碼操作的原子性,讓多線程對同一個數(shù)據(jù)或者資源進行訪問同步。

鎖的分類

這里簡單說下鎖,根據(jù)鎖的狀態(tài)、鎖的特性和鎖的設(shè)計等分為:

  • 公平鎖/非公平鎖
  • 可重入鎖--又名遞歸鎖,一定程度上避免死鎖。
  • 獨享鎖/共享鎖
  • 互斥鎖/讀寫鎖
  • 樂觀鎖/悲觀鎖
  • 分段鎖
  • 偏向鎖/輕量級鎖/重量級鎖
  • 自旋鎖
atomic與nonatomic

說到原子性就會想到屬性關(guān)鍵字中atomic和nonatomic。設(shè)置atomic之后,默認生成的getter和setter方法執(zhí)行是原子的,它只保證了自身的讀/寫操作,卻不能說是線程安全。

  • nonatomic 非原子屬性
  • atomic 原子屬性(線程安全),針對多線程設(shè)計的,默認值
  • 保證同一時間只有一個線程能夠?qū)懭?但是同一個時間多個線程都可以取值)
  • atomic 本身就有一把鎖(自旋鎖)
  • 單寫多讀:單個線程寫入,多個線程可以讀取

atomic:線程安全,需要消耗大量的資源
nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備
我們可以用讀寫鎖來解決,例如:

// 在 OC 中,如果同時重寫 了 setter & getter 方法,系統(tǒng)不再提供 _成員變量,需要使用合成指令
// @synthesize name 取個別名:_name
@synthesize name = _name;
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    /**
     * 增加一把鎖,就能夠保證一條線程在同一時間寫入!
     */
    @synchronized (self) {
        _name = name;
    }
}
  • 互斥鎖小結(jié)

    • 保證鎖內(nèi)的代碼,同一時間,只有一條線程能夠執(zhí)行!
    • 互斥鎖的鎖定范圍,應(yīng)該盡量小,鎖定范圍越大,效率越差!
  • 互斥鎖參數(shù)

    • 能夠加鎖的任意 NSObject 對象
    • 注意:鎖對象一定要保證所有的線程都能夠訪問
    • 如果代碼中只有一個地方需要加鎖,大多都使用 self,這樣可以避免單獨再創(chuàng)建一個鎖對象

關(guān)于線程安全問題,多線程安全比多線程性能更重要,建議用@synchronizedNSLock,可保證可讀性和安全性。

線程的創(chuàng)建

/**
 線程創(chuàng)建的方式
 */
- (void)creatThreadMethod{
    
    NSLog(@"%@", [NSThread currentThread]);
    
    //A: 1:開辟線程
    NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@3];
    // 2. 啟動線程
    [t start];
    t.name = @"學(xué)習(xí)線程";
    
    // detach 分離,不需要啟動,直接分離出新的線程執(zhí)行
    [NSThread detachNewThreadSelector:@selector(study:) toTarget:self.p withObject:@5];
    
    //NSObject (NSThreadPerformAdditions)的分類
    //C : `隱式`的多線程調(diào)用方法,沒有thread,也沒有 start
    self.p = [[Person alloc] init];
    [self.p performSelectorInBackground:@selector(study:) withObject:@10];
    

    // GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self study;];
    });
    
    // NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];
    NSLog(@"%@", [NSThread currentThread]);
}

Person.m文件實現(xiàn)

- (void)study:(id)time{
    for (int i = 0; i<[time intValue]; i++) {
        NSLog(@"%@ 開始學(xué)習(xí)了 %d分鐘",[NSThread currentThread],i);
    }
}

或者

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//0: pthread
        
    /**
     pthread_create 創(chuàng)建線程
     參數(shù):
     1. pthread_t:要創(chuàng)建線程的結(jié)構(gòu)體指針,通常開發(fā)的時候,如果遇到 C 語言的結(jié)構(gòu)體,類型后綴 `_t / Ref` 結(jié)尾
     同時不需要 `*`
     2. 線程的屬性,nil(空對象 - OC 使用的) / NULL(空地址,0 C 使用的)
     3. 線程要執(zhí)行的`函數(shù)地址`
     void *: 返回類型,表示指向任意對象的指針,和 OC 中的 id 類似
     (*): 函數(shù)名
     (void *): 參數(shù)類型,void *
     4. 傳遞給第三個參數(shù)(函數(shù))的`參數(shù)`
     
     返回值:C 語言框架中非常常見
     int
     0          創(chuàng)建線程成功!成功只有一種可能
     非 0       創(chuàng)建線程失敗的錯誤碼,失敗有多種可能!
     */
    // pthread
    pthread_t threadId = NULL;
    //c字符串
    char *cString = "HelloCode";
    // OC prethread -- 跨平臺
    // 鎖
    int result = pthread_create(&threadId, NULL, pthreadTest, cString);
    if (result == 0) {
        NSLog(@"成功");
    } else {
        NSLog(@"失敗");
    }
    // GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self threadTest];
    });
    
    // NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];
}

- (void)threadTest{
    NSLog(@"begin");
    NSInteger count = 1000 * 100;
    for (NSInteger i = 0; i < count; i++) {
        // 棧區(qū)
        NSInteger num = i;
        // 常量區(qū)
        NSString *name = @"zhang";
        // 堆區(qū)
        NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
        NSLog(@"%@", myName);
    }
    NSLog(@"over");
}

void *pthreadTest(void *para){
    // 接 C 語言的字符串
    //    NSLog(@"===> %@ %s", [NSThread currentThread], para);
    // __bridge 將 C 語言的類型橋接到 OC 的類型
    NSString *name = (__bridge NSString *)(para);
    
    NSLog(@"===>%@ %@", [NSThread currentThread], name);
    
    return NULL;
}

TIP:C與OC的橋接

  • __bridge只做類型轉(zhuǎn)換,但是不修改對象(內(nèi)存)管理權(quán);
  • __bridge_retained(也可以使用CFBridgingRetain)將Objective-C的對象轉(zhuǎn)換為Core Foundation的對象,同時將對象(內(nèi)存)的管理權(quán)交給我們,> 后- 續(xù)需要使用CFRelease或者相關(guān)方法來釋放對象;
  • __bridge_transfer(也可以使用CFBridgingRelease)將Core Foundation的對象轉(zhuǎn)換為Objective-C的對象,同時將對象(內(nèi)存)的管理權(quán)交給ARC。

該文章為記錄本人的學(xué)習(xí)路程,也希望能夠幫助大家,知識共享,共同成長,共同進步?。?!文章地址:http://www.itdecent.cn/p/1e69a01c9bfd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容