所謂多線程(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)會在運行、阻塞之間多次切換。
如圖:

多線程實現技術
| 方案 | 簡介 | 生命周期 | 使用頻率 |
|---|---|---|---|
| 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.互斥鎖(同步鎖):
- (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];
}
}