在說(shuō)多線程之前,首先要明白線程蒸眠,進(jìn)程
線程和進(jìn)程
進(jìn)程:
- 系統(tǒng)中正在運(yùn)行的程序漾橙,稱為一個(gè)進(jìn)程
- 每個(gè)進(jìn)程,都在自己獨(dú)立的內(nèi)存空間運(yùn)行楞卡,并且進(jìn)程之間互不影響霜运。
線程:
- 進(jìn)程的所有任務(wù),都是在線程執(zhí)行的蒋腮,可以說(shuō)它是進(jìn)程的基本執(zhí)行單元淘捡,是程序中獨(dú)立運(yùn)行的代碼段。
- 主線程:每個(gè)正在運(yùn)行的程序(即進(jìn)程)池摧,至少包含一個(gè)線程嘱能,這個(gè)線程叫做主線程
- 單線程:只有一個(gè)主線程的程序翘紊,稱作單線程程序
多線程
顧名思義扇单,多線程就是擁有多個(gè)線程的程序色建,iOS用戶可以根據(jù)需要在一段進(jìn)程中開(kāi)辟新的線程誉碴,這些線程相對(duì)于主線程來(lái)說(shuō)被稱為子線程宦棺,子線程和主線程可以并行(同時(shí))執(zhí)行不同的任務(wù)。
原理:
1.同一時(shí)間黔帕,CPU只能處理一條線程代咸,只有一條線程在工作(執(zhí)行)
2.多線程并發(fā)(同時(shí)執(zhí)行,其實(shí)是CPU快速地在多條線程之間)調(diào)度(切換)
3.如果CPU調(diào)度線程的時(shí)間足夠快成黄,就造成了多線程并發(fā)執(zhí)行的假象
4.多核CPU呐芥,每個(gè)核心都可以同時(shí)處理不同任務(wù),從而真正達(dá)到了多線程并發(fā)執(zhí)行任務(wù)
5.線程非常多:
1> CPU會(huì)在N多條線程之間調(diào)度奋岁,CPU會(huì)累死思瘟,消耗大量的CPU資源
2> 每條線程被調(diào)度執(zhí)行的頻次會(huì)降低(線程的執(zhí)行效率降低)
多線程優(yōu)點(diǎn)
1.能適當(dāng)提高程序的執(zhí)行效率
2.能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
多線程缺點(diǎn)
1.開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下闻伶,主線程占用1M,子線程占用512KB)滨攻,如果開(kāi)啟大量的線程,會(huì)占用大量的內(nèi)存空間蓝翰,降低程序的性能
2.線程越多光绕,CPU在調(diào)度線程上的開(kāi)銷就越大
3.程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信,多線程的數(shù)據(jù)共享
多線程在iOS開(kāi)發(fā)中的應(yīng)用
1.什么是主線程
1> 一個(gè)iOS程序運(yùn)行后畜份,默認(rèn)會(huì)開(kāi)啟一條線程诞帐,稱為"主線程"或"UI線程"
2> 每一個(gè)進(jìn)程都有一個(gè)獨(dú)立的主線程
2.主線程的主要作用
1> 顯示\刷新UI界面
2> 處理UI事件 (比如點(diǎn)擊事件、滾動(dòng)事件爆雹、拖拽事件等)
3.主線程的使用注意
1> 別將比較耗時(shí)的操作放到主線程中
2> 耗時(shí)操作會(huì)卡住主線程停蕉,嚴(yán)重影響UI的流暢度愕鼓,給用戶一種"卡"的壞體驗(yàn)
例如:首先隨便在viewController上拖一個(gè)textView或者是寫一個(gè)button的點(diǎn)擊事件,然后在viewDidLoad里寫上一個(gè)稍微耗時(shí)的操作:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (int i = 0; i<10000; i++) {
NSLog(@"---------%d", i);
}
}
那么當(dāng)點(diǎn)擊頁(yè)面后快速的滑動(dòng)textView谷徙,這時(shí)會(huì)發(fā)現(xiàn)拒啰,它是沒(méi)反應(yīng)的
一直等到for循環(huán)走完之后,才會(huì)有反應(yīng)完慧,繼續(xù)執(zhí)行操作谋旦,這就是阻塞主線程。
多線程的實(shí)現(xiàn)方案
技術(shù)方案 | 簡(jiǎn)介 | 語(yǔ)言 | 線程聲明周期 | 使用頻率 |
---|---|---|---|---|
pathread | 一套通用的多線程API</br> 適用于Unix\Linux\Windows等系統(tǒng)</br>跨平臺(tái)\可移植</br>使用難度大 | C | 程序員管理 | 幾乎不用 |
NSThread | 使用更加面向?qū)ο?lt;/br>簡(jiǎn)單易用屈尼,可直接操作線程對(duì)象 | OC | 程序員管理 | 偶爾使用 |
GCD | 旨在替代NSThread等線程技術(shù)</br>充分利用設(shè)備的多核 | C | 自動(dòng)管理 | 經(jīng)常使用 |
NSOperation | 基于GCD(底層是GCD)</br>比GCD多了一些簡(jiǎn)單實(shí)用的功能</br>實(shí)用更加面向?qū)ο?/td> | OC | 自動(dòng)管理 | 經(jīng)常使用 |
pthread的創(chuàng)建:
//創(chuàng)建
pthread_t myRestrict;
pthread_create(&myRestrict, NULL, run, NULL);
void *run(void *data){
NSLog(@"*******%@",[NSThread currentThread]);
return NULL;
}
因?yàn)椴怀S貌嶙牛蛔龆嘟忉專瑥拇蛴〗Y(jié)果可以看出脾歧,開(kāi)辟了子線程
NSThread創(chuàng)建和啟動(dòng)線程的3種方式:
#pragma mark ---- 1.使用NSThread手動(dòng)開(kāi)辟子線程 ----
// 1甲捏、創(chuàng)建線程(NSThread)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downLoad:) object:"http//:abcde.png"];
//線程的名字
thread.name = @"下載線程";
// 2、開(kāi)啟線程
[thread start];
// 3鞭执、關(guān)閉線程(可寫可不寫)
[NSThread exit];
// 4司顿、取消線程(實(shí)際上就是做了個(gè)標(biāo)記,表示被取消了)
[thread cancel];
#pragma mark ---- 2.使用NSThread自動(dòng)開(kāi)辟子線程(無(wú)需手動(dòng)) ----
[NSThread detachNewThreadSelector:@selector(downLoad:) toTarget:self withObject:"http//:abcde.png"];
[thread start];
#pragma mark ---- 3.使用NSObject開(kāi)辟子線程(在后臺(tái)執(zhí)行某個(gè)方法)兄纺,也叫隱式創(chuàng)建(無(wú)需手動(dòng)) ----
/*
[self performSelector:@selector(downLoad:) withObject:@"http//:abcde.png"];
//相當(dāng)于下面這段代碼
[self downLoad:@"http//:abcde.png"];
#因此大溜,這個(gè)方法是不開(kāi)辟子線程的
*/
[self performSelectorInBackground:@selector(downLoad:) withObject:@"http//:abcde.png"];
//這個(gè)方法開(kāi)辟子線程
常見(jiàn)的方法:
1> 獲得當(dāng)前線程
+ (NSThread *)currentThread;
2> 獲得主線程
+ (NSThread *)mainThread;
3>睡眠(暫停)線程
+(void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti
------
//回到主線程刷新數(shù)據(jù)
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
-(void)reloadData{
//刷新UI
if ([NSThread isMainThread]) {
//這里做一些需要的操作
}
}
利用上面的一些方法,可以做一些簡(jiǎn)單的多線程之間的通信估脆,例如開(kāi)辟子線程下載圖片钦奋,回到主線程刷新,這里不再操作
多線程的安全隱患
1.資源共享
1> 1塊資源可能會(huì)被多個(gè)線程共享疙赠,也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
2> 比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象付材、同一個(gè)變量、同一個(gè)文件
2.當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí)圃阳,很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題
安全隱患解決--加鎖(互斥鎖)
1.互斥鎖的優(yōu)缺點(diǎn)
1> 優(yōu)點(diǎn):能有效的防止多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題
2> 缺點(diǎn):需要消耗大量的CPU資源
2.互斥鎖的使用前提:多條線程搶奪同一塊資源
線程同步的意思是:多條線程實(shí)在同一條線上執(zhí)行(按順序的執(zhí)行任務(wù))
互斥鎖厌衔,就是使用了線程同步技術(shù)
單純的靠文字?jǐn)⑹隹赡軙?huì)比較難理解,可以參考以下例子:寫一個(gè)賣票的簡(jiǎn)單程序
- (void)viewDidLoad
{
[super viewDidLoad];
self.leftTicketCount = 50;
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread1.name = @"1號(hào)窗口";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2.name = @"2號(hào)窗口";
self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread3.name = @"3號(hào)窗口";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
/**
* 賣票
*/
- (void)saleTicket
{
while (1) {
int count = self.leftTicketCount;
if (count > 0) {
[NSThread sleepForTimeInterval:0.05];
self.leftTicketCount = count - 1;
NSLog(@"%@賣了一張票, 剩余%d張票", [NSThread currentThread].name, self.leftTicketCount);
} else {
return; // 退出循環(huán)
}
}
}
運(yùn)行結(jié)果:
這就是多線程訪問(wèn)同一塊資源造成的
加鎖:在剛才的方法里加上@synchronized
- (void)saleTicket
{
while (1) {
// ()小括號(hào)里面放的是鎖對(duì)象
@synchronized(self) { // 開(kāi)始加鎖
int count = self.leftTicketCount;
if (count > 0) {
[NSThread sleepForTimeInterval:0.05];
self.leftTicketCount = count - 1;
NSLog(@"%@賣了一張票, 剩余%d張票", [NSThread currentThread].name, self.leftTicketCount);
} else {
return; // 退出循環(huán)
}
} // 解鎖
}
}
GCD的內(nèi)容較多捍岳,我會(huì)在下篇文章里詳細(xì)介紹
NSOperation
1.首先富寿,它是一個(gè)抽象類,所以執(zhí)行任務(wù)的是它的子類:NSInvocationOperation和NSBlockOperation(還有:自定義子類繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的?法)祟同。這兩個(gè)子類作喘,相當(dāng)于一個(gè)方法選擇器“prefromSelector()”,由它倆本身發(fā)起的任務(wù)晕城,并不是在子線程中執(zhí)行泞坦。
2.NSOperation和它的子類,本身并不會(huì)進(jìn)行線程的創(chuàng)建砖顷,所以贰锁,在他們的任務(wù)方法中打印當(dāng)前線程赃梧,顯示為主線程
3.NSOperation和它的子類,只是一個(gè)操作豌熄,本身沒(méi)有主線程授嘀、子線程之分,可以在任何線程中使用锣险,通常和NSOperationQueue結(jié)合使用蹄皱。
NSOperationQueue
1、一個(gè)NSOperationQueue操作隊(duì)列芯肤,就相當(dāng)于一個(gè)線程管理器巷折,將NSOperation和子類的對(duì)象放入隊(duì)列中,然后由隊(duì)列負(fù)責(zé)派發(fā)任務(wù)崖咨,所以NSOperationQueue并不是一個(gè)線程锻拘。但是,你可以設(shè)置隊(duì)列中運(yùn)行的線程的數(shù)量
---- 優(yōu)點(diǎn) ----
不需要手動(dòng)關(guān)聯(lián)線程击蹲,只需要把精力放在自己要執(zhí)行的操作上面署拟。
---- 缺點(diǎn) ----
它是基于OC對(duì)象的,那么相對(duì)于C函數(shù)來(lái)說(shuō)效率要低
配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線程編程
//子類一:NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
//operation在單獨(dú)使用的時(shí)候,需要手動(dòng)調(diào)用開(kāi)啟方法
[operation start];
#operation直接調(diào)用start歌豺,是同步執(zhí)行(在當(dāng)前線程中執(zhí)行操作)推穷,說(shuō)白了就相當(dāng)于[self hehehe];并沒(méi)有什么卵用
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
[queue addOperation:operation];//********注意:如果搭配了NSOperationQueue中的add方法創(chuàng)建多線程的話,就不需要使用satrt方法,否則會(huì)崩潰
//子類二:NSBlockOperation
//任務(wù)數(shù)量 > 1,才會(huì)開(kāi)始異步執(zhí)行
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block:%@",[NSThread currentThread]);
NSLog(@"block:%@",[NSThread mainThread]);
NSLog(@"block:%d",[NSThread isMainThread]);
NSLog(@"*********");
}];
//開(kāi)啟
[blockOperation start];
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block:%@",[NSThread currentThread]);
NSLog(@"block:%@",[NSThread mainThread]);
NSLog(@"block:%d",[NSThread isMainThread]);
NSLog(@"*********");
}];
[queue addOperation:blockOperation];
//簡(jiǎn)單的做法:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"*******%@", [NSThread currentThread]);
}];
#------設(shè)置最大并發(fā)數(shù)-----
//當(dāng)設(shè)置最大并發(fā)數(shù)為1時(shí) :也可叫做串行,順序執(zhí)行
//當(dāng)設(shè)置最大并發(fā)數(shù)大于1時(shí):叫并行世曾,多條通道同時(shí)進(jìn)行各自的任務(wù)缨恒,互不影響谴咸。
queue.maxConcurrentOperationCount = 3;
除了GCD轮听,其他兩種主要的創(chuàng)建多線程的方法基本已經(jīng)介紹完了,如果覺(jué)得有什么問(wèn)題或者錯(cuò)誤岭佳,歡迎留言或發(fā)簡(jiǎn)信Q !