進程與線程
如果把進程比作是一個電子廠猫胁,那么線程就是一條條的流水作業(yè)線名眉。電子廠與電子廠之間相互獨立,當前電子廠的作業(yè)流水線只能使用自己電子廠資源。
進程
- 進程是指在系統(tǒng)中正在運行的一個應用程序鸣戴,比如打開的
Xcode
。 - 每個進程之間是獨立的粘拾,每個進程運行在專有的而且受保護的內(nèi)存空間中窄锅。
線程
- 線程是進程的基本執(zhí)行單元,一個進程的所有任務都在線程中執(zhí)行缰雇。
- 進程想要執(zhí)行任務入偷,必須要有線程,進程至少要有一條線程用來執(zhí)行任務械哟。
- 程序啟動時會默認開啟一條線程疏之,這條線程被稱為主線程或者
UI
線程。
進程與線程的關系
- 線程是進程的執(zhí)行單元戒良,進程的所有任務都在線程中執(zhí)行体捏,同一個進程內(nèi)的線程共享進程資源。
- 地址空間:同一進程的線程共享本進程的地址空間糯崎,而進程之間則是獨立的地址空間几缭。
- 資源擁有:同一進程內(nèi)的線程共享本進程的資源如內(nèi)存、
I/O
沃呢、cpu
等年栓,但是進程之間的 資源是獨立的。 - 一個進程崩潰后薄霜,在保護模式下不會對其他進程產(chǎn)生影響某抓,但是一個線程崩潰整個進 程都死掉。所以多進程要比多線程健壯惰瓜。
作為一個開發(fā)者否副,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:834688868崎坊,不管你是大牛還是小白都歡迎入駐 备禀,分享BAT,阿里面試題、面試經(jīng)驗奈揍,討論技術曲尸, 大家一起交流學習成長!
如果你正在面試男翰,或者正準備跳槽另患,不妨看看我精心總結的面試資料: BAT 大廠最新面試題+答案合集(持續(xù)更新中) 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障
- 進程切換時,消耗的資源大蛾绎,效率高昆箕。所以涉及到頻繁的切換時鸦列,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作为严,只能用線程不能用進程敛熬。
- 執(zhí)行過程:每個獨立的進程有一個程序運行的入口、順序執(zhí)行序列和程序入口第股。但是 線程不能獨立執(zhí)行应民,必須依存在應用程序中,由應用程序提供多個線程執(zhí)行控制夕吻。
- 線程是處理器調(diào)度的基本單位诲锹,但是進程不是。
- 線程沒有地址空間,線程包含在進程地址空間中涉馅。
多線程
多線程原理
我們知道一個進程可以開啟多個線程归园,進程的所有任務都在線程中執(zhí)行,而一個線程中的任務是串行的稚矿,如果要在一個線程中執(zhí)行多個任務庸诱,那么只能一個一個地按順序執(zhí)行這些任務,也就是說晤揣,在同一時間內(nèi)桥爽,一個線程只能執(zhí)行一個任務,而在同一時刻昧识,一個CPU
只能處理一條線程(只有一個線程在執(zhí)行任務)钠四,但CPU
可以在多條線程之間快速的切換,只要切換的足夠快跪楞,就造成了多線程一同執(zhí)行的假象缀去。
多線程的優(yōu)缺點
優(yōu)點
- 能適當提高程序的執(zhí)行效率
- 能適當提高資源的利用率(
CPU
,內(nèi)存) - 線程上的任務執(zhí)行完成后甸祭,線程會自動銷毀
缺點
- 開啟線程需要占用一定的內(nèi)存空間(默認情況下缕碎,主線程占用
1 MB
,子線程都占用512 KB
) - 如果開啟大量的線程池户,會占用大量的內(nèi)存空間咏雌,降低程序的性能
- 線程越多,
CPU
在調(diào)用線程上的開銷就越大 - 程序設計更加復雜煞檩,比如線程間的通信处嫌、多線程的數(shù)據(jù)共享
那么提出一個疑問?如果進程開啟的線程非常非常多栅贴,會發(fā)生什么情況?
答:CPU
會在許多線程之間調(diào)度斟湃,CPU
會累死,會消耗大量的CPU
資源, 而且每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率也就降低)
主線程(UI
線程)
一個iOS
程序運行后檐薯,默認會開啟一條線程凝赛,稱為主線程
或UI線程
.主線程主要用于顯示刷新UI
界面注暗,處理UI
事件。(最好不要將耗時任務放在主線程處理墓猎,耗時操作會卡住主線程捆昏,造成一種卡頓現(xiàn)象。)
線程的生命周期
- 新建:實例化線程對象
- 就緒:向線程對象發(fā)送
start
消息毙沾,線程對象并不會立即執(zhí)行骗卜,線程對象被加入可調(diào)度線程池等待CPU
調(diào)度。 - 運行:
CPU
負責調(diào)度可調(diào)度線程池中線程的執(zhí)行左胞。線程執(zhí)行完成之前寇仓,狀態(tài)可能會在就緒和運行之間來回切換。就緒和運行之間的狀態(tài)變化由CPU
負責烤宙,程序員不能干預遍烦。 - 阻塞:當滿足某個預定條件時,可以使用休眠或鎖躺枕,阻塞線程執(zhí)行服猪。當進入休眠時,會重新將線程加入就緒狀態(tài)中拐云。休眠的時間設置參數(shù)為:
sleepForTimeInterval
(休眠指定時長)罢猪,sleepUntilDate
(休眠到指定日期),@synchronized(self)
:(互斥鎖)慨丐。 - 死亡:正常死亡坡脐,線程執(zhí)行完畢。非正常死亡房揭,當滿足某個條件后备闲,在線程內(nèi)部中止執(zhí)行(或者在主線程中止線程對象)
關于線程的exit
和cancel
:
[NSThread exit]:一旦強行終止線程,后續(xù)的所有代碼都不會執(zhí)行
[thread cancel]:并不會直接取消正在執(zhí)行的線程捅暴,只是給線程對象添加 isCancelled 標記
線程的優(yōu)先級
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
復制代碼
上述優(yōu)先級從高到低恬砂,但是,線程執(zhí)行的快慢蓬痒,除了看線程的優(yōu)先級泻骤,還需要查看執(zhí)行任務資源的大小(即任務的復雜度)梧奢、以及 CPU
調(diào)度情況狱掂。
線程池
線程安全
當多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題亲轨。就好像售票系統(tǒng)趋惨,如果多人同時在售票,每個人的售票處理的速度不一樣惦蚊,那么就會造成余票的數(shù)量飄忽不定器虾。 那么解決多線程安全問題有兩種方法:互斥鎖和自旋鎖讯嫂。
互斥鎖和自旋鎖
互斥鎖(同步鎖):@synchronized
@synchronized(鎖對象) {
// 需要鎖定的代碼
}
復制代碼
- 用于保護臨界區(qū),保證鎖內(nèi)的代碼,同一時間兆沙,只有一條線程能夠執(zhí)行欧芽。
- 判斷的時候鎖對象要存在,如果代碼中只有一個地方需要加鎖葛圃,大多都使用
self
作為鎖對象千扔,這樣可以避免單獨再創(chuàng)建一個鎖對象。 - 加了互斥鎖的代碼库正,當有新的線程訪問時昏鹃,如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定的代碼,新線程就會進入休眠诀诊。
- 鎖對象一定要保證所有的線程都能夠訪問洞渤。
- 互斥鎖的鎖定范圍,應該盡量小属瓣,鎖定范圍越大载迄,效率越差。
自旋鎖
自旋鎖不同于互斥鎖通過線程休眠來達到阻塞抡蛙,自旋鎖是線程在獲取鎖對象之前护昧,一直處于忙等詢問的阻塞狀態(tài)。
加了自旋鎖粗截,當新線程訪問代碼時惋耙,如果發(fā)現(xiàn)有其他線程正在鎖定代碼,新線程會用死循環(huán)的方式熊昌,一直等待鎖定的代碼執(zhí)行完成绽榛。相當于不停嘗試執(zhí)行代碼,比較消耗性能婿屹。其中灭美,屬性修飾符atomic
,本身就有一把自旋鎖(atomic
又稱為原子鎖)昂利。
atomic
和nonatomic
atomic
原子屬性,是默認屬性届腐,是線程安全的,保證同一時間只有一個線程能夠寫入蜂奸,但是同一個時間多個線程都可以取值犁苏。使用其需要消耗大量的資源。
nonatomic
非原子屬性,是非線程安全的扩所,同一時間可以有很多線程讀和寫围详。相比atomic
效率更高。
在iOS
開發(fā)的過程中碌奉,建議將所有屬性都聲明為nonatomic
短曾,開發(fā)過程中盡量避免多線程搶奪同一資源,將資源的業(yè)務邏輯交由服務端完成赐劣。
線程之間的通信
在蘋果的文檔Threading Programming Guide文檔的Table 1-3 Communication mechanisms
部分嫉拐,有提到關于線程之間通信的方式。
簡單用代碼介紹一下常用的通信方式:
- 直接消息: 通過
performSelector
的一系列方法
//異步下載圖像
[self performSelectorInBackground:@selector(downloadImageWithURL:) withObject:url];
- (void)downloadImageWithURL:(NSURL *)url {
// 1\. 獲取二進制數(shù)據(jù)
NSData *data = [NSData dataWithContentsOfURL:url];
// 2\. 將二進制數(shù)據(jù)轉換成 image
UIImage *image = [UIImage imageWithData:data];
// 3\. 在主線程更新 UI
// waitUntilDone: 是否等待 updateImage: 執(zhí)行完成
[self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:YES];
NSLog(@"完成");
}
復制代碼
- 端口通信
ZhModel.h
@interface ZhModel : NSObject
- (void)modelLaunchThreadWithPort:(NSPort *)port;
@end
ZhModel.m
#import "ZhModel.h"
@interface ZhModel()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation ZhModel
- (void)modelLaunchThreadWithPort:(NSPort *)port{
NSLog(@"VC 響應了Model里面");
@autoreleasepool {
//1\. 保存主線程傳入的port
self.vcPort = port;
//2\. 設置子線程名字
[[NSThread currentThread] setName:@"ZhModelThread"];
//3\. 開啟runloop
[[NSRunLoop currentRunLoop] run];
//4\. 創(chuàng)建自己port
self.myPort = [NSMachPort port];
//5\. 設置port的代理回調(diào)對象
self.myPort.delegate = self;
//6\. 完成向主線程port發(fā)送消息
[self sendPortMessage];
}
}
// 完成向主線程發(fā)送port消息
- (void)sendPortMessage {
NSData *data1 = [@"ZhModel" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
// 發(fā)送消息到VC的主線程
// 第一個參數(shù):發(fā)送時間魁兼。
// msgid 消息標識婉徘。
// components,發(fā)送消息附帶參數(shù)咐汞。
// reserved:為頭部預留的字節(jié)數(shù)
[self.vcPort sendBeforeDate:[NSDate date]
msgid:10086
components:array
from:self.myPort
reserved:0];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"model:handlePortMessage == %@",[NSThread currentThread]);
NSLog(@"從VC 傳過來一些信息:");
NSLog(@"components == %@",[message valueForKey:@"components"]);
NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}
@end
復制代碼
PortViewController.m
#import "PortViewController.h"
#import <objc/runtime.h>
#import "ZhModel.h"
@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) ZhModel *zhmodel;
@end
@implementation PortViewController
- (void)viewDidLoad {
[super viewDidLoad];
//1\. 創(chuàng)建主線程的port
// 子線程通過此端口發(fā)送消息給主線程
self.myPort = [NSMachPort port];
//2\. 設置port的代理回調(diào)對象
self.myPort.delegate = self;
//3\. 把port加入runloop盖呼,接收port消息
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.zhmodel = [[ZhModel alloc] init];
[NSThread detachNewThreadSelector:@selector(modelLaunchThreadWithPort:)
toTarget:self.zhmodel
withObject:self.myPort];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC == %@",[NSThread currentThread]);
NSLog(@"從person 傳過來一些信息:");
NSArray *messageArr = [message valueForKey:@"components"];
NSString *dataStr = [[NSString alloc] initWithData:messageArr.firstObject encoding:NSUTF8StringEncoding];
NSLog(@"傳過來一些信息 :%@",dataStr);
NSPort *destinPort = [message valueForKey:@"remotePort"];
if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
NSLog(@"傳過來的數(shù)據(jù)有誤");
return;
}
NSData *data = [@"VC收到!!!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]];
// 非常重要,如果你想在Person的port接受信息,必須加入到當前主線程的runloop
[[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];
NSLog(@"Thread == %@",[NSThread currentThread]);
BOOL success = [destinPort sendBeforeDate:[NSDate date]
msgid:10010
components:array
from:self.myPort
reserved:0];
NSLog(@"%d",success);
}
@end
復制代碼
多線程的實現(xiàn)方式
多線程的四種實現(xiàn)方式分別是:pthread
,NSThread
化撕,GCD
几晤, NSOperation
。
下面通過代碼來看一下這四種實現(xiàn)方式:
pthread
/**
pthread_create 創(chuàng)建線程
參數(shù):
1\. pthread_t:要創(chuàng)建線程的結構體指針植阴,通常開發(fā)的時候蟹瘾,如果遇到 C 語言的結構體,類型后綴 `_t / Ref` 結尾
同時不需要 `*`
2\. 線程的屬性掠手,nil(空對象 - OC 使用的) / NULL(空地址憾朴,0 C 使用的)
3\. 線程要執(zhí)行的`函數(shù)地址`
void *: 返回類型,表示指向任意對象的指針喷鸽,和 OC 中的 id 類似
(*): 函數(shù)名
(void *): 參數(shù)類型众雷,void *
4\. 傳遞給第三個參數(shù)(函數(shù))的`參數(shù)`
返回值:int
0 創(chuàng)建線程成功!成功只有一種可能
非 0 創(chuàng)建線程失敗的錯誤碼做祝,失敗有多種可能砾省!
*/
pthread_t threadId = NULL;
char *cString = "HelloWorld";
int result = pthread_create(&threadId, NULL, pthreadTest, cString);
if (result == 0) {
NSLog(@"成功");
} else {
NSLog(@"失敗");
}
void *pthreadTest(void *para){
// __bridge 將 C 語言的類型橋接到 OC 的類型
NSString *name = (__bridge NSString *)(para);
NSLog(@"===>%@ %@", [NSThread currentThread], name);
return NULL;
}
復制代碼
2.NSThread
[NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
復制代碼
GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self threadTest];
});
復制代碼
NSOperation
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
[self threadTest];
}];
- (void)threadTest{
NSLog(@"begin");
NSLog(@"over");
}
未完待續(xù)......
作者:Henry_Jeannie
鏈接:https://juejin.cn/post/6975035560607875080
來源:掘金