多線程

所謂多線程(multithreading),是指從軟件或者硬件上實現多個線程并發(fā)執(zhí)行的技術。具有多線程能力的計算機因有硬件支持而能夠在同一時間執(zhí)行多于一個線程,進而提升整體處理性能。

多線程的基本概念

  • 進程:可以理解為系統中正在運行的一個應用程序的實例,這個實例通過Process ID(進程ID,PID)進行唯一標識。同一個可執(zhí)行文件可以并發(fā)地啟動多個實例,但是每個實例的PID是不同的。
  • 線程:是現代操作系統操作的基本單元,線程只不過是一組寄存器的狀態(tài),一個進程可以存在多個線程。一個進程中的所有線程都共享虛擬內存空間、文件描述符和各種句柄。
  • 同步:在當前線程中執(zhí)行任務,不具備開啟線程的能力。
  • 異步:在新的線程中執(zhí)行任務,具備開啟新線程的能力。
  • 串行:執(zhí)行任務時,一個任務的執(zhí)行必須等到前一個的任務完成。
  • 并行:執(zhí)行任務時,可以同時執(zhí)行多個任務。
  • 主線程:在運行之后,會默認開啟一條線程,稱為“主線程”或“UI線程”,主線程是串行的。主線程的主要作用:顯示/刷新UI界面,處理UI事件(比如點擊事件、滾動事件、拖拽事件),這里需要注意的是:不要將耗時操作放在主線程。
  • 子線程:主線程以外的線程。子線程的作用:主要是執(zhí)行耗時操作。

多線程的原理

我們知道,在同一時間內,CPU只能處理一條線程,即只有一條線程在執(zhí)行。多線程的并發(fā)執(zhí)行,就是CPU在多條線程之間實現快速的調度(切換),使得看起來就好像是一起在執(zhí)行。

使用多線程可以適當提高程序的執(zhí)行效率以及資源利用率(CPU、內存利用率),但是開啟線程需要占用一定的內存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,就會占用大量的內存空間,降低程序的性能。隨著線程數量越多,CPU在調度線程上的開銷就越大。

線程的狀態(tài)與生命周期

線程的生命周期要經過新建、就緒、運行、阻塞死亡5種狀態(tài)。當創(chuàng)建線程并啟動以后,它并不是一啟動就進入了運行狀態(tài)。尤其是當線程啟動以后,它不可能一直"霸占"著CPU獨自運行,CPU需要在多條線程之間切換,所以線程狀態(tài)會在運行、阻塞之間多次切換。
如圖:

thread_life_cycle.png

多線程實現技術

方案 簡介 生命周期 使用頻率
pthread 純C語言,跨平臺 程序員管理 幾乎不用
NSThread OC語言,使用面向對象的思維,直接操縱線程對象 程序員管理 偶爾使用
GCD 純C語言,用于取代NSThread,可用充分利用設備的多核 自動管理 經常使用
NSOperation 基于GCD,更面向對象 自動管理 經常使用

多線程安全隱患

資源共享問題:一塊資源有可能會被多個線程共享,即多個線程同時訪問一個資源。想要解決資源的話,就需要加鎖。加鎖可以保證當一個線程正在對一塊資源進行寫操作的時候,這時候是不允許其他的線程對該資源進行訪問,只有當該線程訪問結束后,其他線程才能按順序進行訪問。

以賣票問題為例,代碼如下:

@interface Multithread()

@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;
@property (nonatomic, strong) NSThread *thread3;

@property (nonatomic, assign) NSInteger ticketCount;

@end

@implementation Multithread

- (instancetype)init {
    if (self = [super init]) {
        self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(_sellTickets) object:nil];
        self.thread1.name = @"thread1";
        
        self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(_sellTickets) object:nil];
        self.thread2.name = @"thread2";
        
        self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(_sellTickets) object:nil];
        self.thread3.name = @"thread3";
        
        self.ticketCount = 100;
    }
    
    return self;
}

- (void)begin {
    [self.thread1 start];
    [self.thread2 start];
    [self.thread3 start];
}

- (void)_sellTickets {
    while (self.ticketCount > 0) {
            [NSThread sleepForTimeInterval:1];
            
            if (self.ticketCount > 0) {
                self.ticketCount --;
                NSLog(@"%@買了一張票,還有%ld", [NSThread currentThread].name, self.ticketCount);
            }
    }
}

@end

打印出來的結果:

1.jpg

從上面可以看出打印的余票數據出現錯誤。

解決方法:

1.互斥鎖(同步鎖):

- (void)_sellTickets {
    while (self.ticketCount > 0) {
        @synchronized(self) {
            [NSThread sleepForTimeInterval:1];
            
            if (self.ticketCount > 0) {
                self.ticketCount --;
                NSLog(@"%@買了一張票,還有%ld", [NSThread currentThread].name, self.ticketCount);
            }
        }
    }
}

當有線程在執(zhí)行代碼的時候,執(zhí)行體也就是代碼塊就會被加鎖,等這個線程處理完成后,下一個線程才能使用,相當于將并行改成了串行,另外互斥鎖在保護單例的時候可以使用。它的缺點就是消耗性能。

2.NSLock

- (void)_sellTickets {
    while (self.ticketCount > 0) {
        [self.lock lock];
        [NSThread sleepForTimeInterval:1];
        
        if (self.ticketCount > 0) {
            self.ticketCount --;
            NSLog(@"%@買了一張票,還有%ld", [NSThread currentThread].name, self.ticketCount);
        }
        [self.lock unlock];
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容