iOS多線程的學(xué)習(xí)總結(jié)
一個程序可有多個進(jìn)程案腺,一個進(jìn)程,可有多個線程康吵。且至少有一個線程劈榨。線程內(nèi)部是串行,但是每條線程也可以(并行)同時執(zhí)行不同任務(wù)晦嵌。
進(jìn)程相當(dāng)于工廠車間(內(nèi)存劃分空間)同辣,然后線程相當(dāng)于在里面干活的人拷姿,多線程,就是在這空間多個干活的人旱函!
關(guān)于多線程的好處
- 適當(dāng)提高程序的效率
- 適當(dāng)提高資源利用旅(cpu响巢,內(nèi)存利用率)
壞處
- 創(chuàng)建線程“舴粒空間上沒的消耗:椬俟牛空間512kb ,主線程1mb券腔,事件消耗90 毫秒伏穆。一般開3~5條線程
- 開啟大量線程會降低性能
- cpu消耗加大
- 程序更加復(fù)雜
多線程的實(shí)現(xiàn)方案
1.pthread 跨平臺,可移植颅眶。幾乎不用
2.NSThread : 面向?qū)ο篁诔觯唵我子茫芍苯硬僮骶€程對象涛酗,但是生命周期需要程序員管理
3.GCD : 代替NSThread 多線程技術(shù)铡原,充分利用設(shè)備的多核(雙核,四核)商叹,生命周期不需要程序員管理燕刻,系統(tǒng)自動管理
4.NSOperation :基于gcd(底層是gcd),比gcd多了一些簡單實(shí)用功能剖笙,使用面向?qū)ο?卵洗,生命周期,自動管理弥咪,不需要程序員管理
如何獲得主線程
[NSThread mainThread];
NSTread 的基本使用
創(chuàng)建方式一:
//創(chuàng)建線程的方式1
-(void)creatNewThread1
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"創(chuàng)建方法1"];
//線程對象的一些屬性
thread.name = @"線程A";
thread.threadPriority = 0.9 ; //優(yōu)先級 0.0 ~ 1.0
[thread start]; //手動啟動線程
}
-(void)run:(NSString *)parm
{ NSLog(@"parm = %@" ,parm) ;
//當(dāng)前線程
[NSThread currentThread];
[NSThread isMainThread]; //是否是主線程
[NSThread mainThread];//獲取主線程
}
創(chuàng)建方式二:
//創(chuàng)建線程的方式2过蹂,,分離子線程
-(void)creatNewThread2{
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分離子線程"];
}
創(chuàng)建方式三:
//創(chuàng)建線程的方式3,,這種方式不需要手動開啟線程
-(void)creatNewThread2{
[self performSelectorInBackground:@selector(run:) withObject:@"開啟后臺線程"];
}
創(chuàng)建方式四:繼承NSTread子類聚至。重寫main 酷勺,把任務(wù)封裝到main中
//從些 NSThread 子類,重寫main 方法扳躬,把任務(wù)封裝到main方法中
#import <Foundation/Foundation.h>
@interface XCThread : NSThread
@end
#import "XCThread.h"
@implementation XCThread
-(void)main
{
[super main];
NSLog(@"666");
}
@end
-(void)threadCreat4
{
XCThread *tread = [[XCThread alloc] init];
[tread start];
}
/* 打印
2017-11-15 22:52:48.615 NSTread基本使用[1003:32546] 666
2017-11-15 22:52:48.615 NSTread基本使用[1003:32546] 666
*/
如果想要創(chuàng)建多個線程只需要脆诉,把創(chuàng)建線程的代碼多執(zhí)行幾次就可以了 ,那么 1 相對于2贷币,3 方法好處呢击胜,那就是可以拿到線程對象,操作線程役纹。反正亦然偶摔。NStread的線程的生命周期,當(dāng)線程中的人物執(zhí)行完畢促脉,被釋放辰斋。
新建 就緒 運(yùn)行 阻塞 死亡 五中狀態(tài)
-(void)start , 線程進(jìn)入就緒狀態(tài) -> cpu來調(diào)度就會進(jìn)入運(yùn)行狀態(tài)信不,人物完畢,自動進(jìn)入死亡
//阻塞暫停線程 亡呵,設(shè)置睡眠時間
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//例子,讓線程睡
[NSThread sleepForTimeInterval:2.0];
//強(qiáng)制停止線程
+(void)exit;
//死亡狀態(tài)硫戈,一旦線程死亡了锰什。就不能再次開啟這個線程
注意線程安全,經(jīng)典的賣票例子
//解決辦法加互斥鎖
//多個線程調(diào)用這個丁逝,賣票方法汁胆,即多個窗口賣票一樣
//賣票方法
-(void)saleTickt
{
while (1) {
//互斥鎖 :鎖必須是全局唯一的。比如self 就可以霜幼,其他全局的也可以@synchronized (必須全局才行)
//1,注意加鎖的位置
//2嫩码,注意加鎖的前提條件
//3,加鎖代價罪既,耗費(fèi)性能
//4铸题,加鎖結(jié)果,線程同步琢感。一開始多個線程是異步的
@synchronized (self) {
//鎖里面的方法
if (_count > 0) {
_count = _count - 1;
NSLog(@"賣出去一張票");
}else{
NSLog(@"騷年沒有票了");
}
}
}
}
互斥鎖
@synchronized (必須全局才行)
{
需要鎖定的代碼
}
*優(yōu)點(diǎn):能有效防止多線程搶同一塊資源丢间,造成同步存取數(shù)據(jù)的不安全。
*缺點(diǎn):需要耗費(fèi)大量的cpu資源
線程同步:多條線程同一條線上執(zhí)行驹针,實(shí)現(xiàn)方法加互斥鎖烘挫。默認(rèn)情況下,多條線程是異步執(zhí)行的柬甥。
原子和非原子屬性的選擇
nonatomic :非線程安全饮六,不會set 方法加鎖
atomic 非原子屬性,線程安全苛蒲,會給set 加鎖需要耗費(fèi)較大資源加鎖
線程間的通信
- 一個線程傳遞數(shù)據(jù)給另一個線程
- 一個線程執(zhí)行任務(wù)完成后卤橄,轉(zhuǎn)到另一個線程繼續(xù)執(zhí)行任務(wù)
線程間的通信常用方法
//回到主線程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// onThread : 線程對象可回主線程可回子線程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
比如子線程下載圖片,下載完成圖片后回到主線程中進(jìn)行加載顯示的操作
//子線程下載圖片
-(void)downLoadImage
{
NSString *str = @"http://static.firefoxchina.cn/img/201710/4_59e999c8e6aa50.jpg";
NSURL *url = [NSURL URLWithString:str];
CFTimeInterval currentTime = CFAbsoluteTimeGetCurrent() ;
//下載網(wǎng)絡(luò)圖片
NSData *iamgeData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:iamgeData];
//回到主線程方法 ********方法1***********
/*
1,第一個參數(shù):回到主線程需要調(diào)用那個方法
2.第二個參數(shù):前面方法需要傳遞的參數(shù)
3.第三個參數(shù):是否等待 ,撤防。如果為yes虽风,執(zhí)行到了 回到主線程performSelectorOnMainThread,
會立馬打印 x-x-x 寄月,如果為NO辜膝,那么會等showImage:方法執(zhí)行完畢才會打印x-x-x
*/
//方式1:回到主線程調(diào)用刷新,和方式2 取其一
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
//方式2漾肮,回到主線程刷新厂抖,這種方式更靈活,也可以和子線程通信
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
NSLog(@"x-x-x");
}
//主線程顯示圖片
-(void)showImage:(UIImage *)image
{
//顯示imageview
self.imageview.image = image ;
}
常駐子線程
需求克懊。一開始我們創(chuàng)建一個子線程 tread 忱辅。調(diào)用方法test 打印七蜘。結(jié)果是開啟了子線程并且打印了。因?yàn)樽泳€程任務(wù)執(zhí)行完畢墙懂。立馬就銷毀了 橡卤,雖然tread 是被strong強(qiáng)指針修飾,但是已經(jīng)沒有用死掉了损搬。所以我們點(diǎn)擊屏幕調(diào)用test1 方法碧库。在 tread線程里面調(diào)用。會報錯巧勤。嵌灰。下面是錯誤的寫法
@interface ViewController ()
@property (nonatomic,strong)NSThread * tread;
@end
@implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
//1,創(chuàng)建線程
self.tread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[self.tread start];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 線程通信。也在 子線程 tread 線程調(diào)用test1 颅悉。
//因?yàn)樽泳€程在執(zhí)行 test 的時候沽瞭,任務(wù)完成立馬就銷毀了坛梁。所以會crash .那么怎么解決呢披坏。常駐子線程可以解決
[self performSelector:@selector(test1) onThread:self.tread withObject:nil waitUntilDone:YES];
}
-(void)test
{
NSLog(@"%s %@ ",__func__ ,[NSThread currentThread]);
}
-(void)test1
{
NSLog(@"%s %@ ",__func__ ,[NSThread currentThread]);
}
那么我們該如何保持子線程不銷毀呢民珍?所以我們需要常駐子線程 ?? 玛瘸,應(yīng)用場景如網(wǎng)絡(luò)請求等公般。
@interface ViewController ()
@property (nonatomic,strong)NSThread * tread;
@end
@implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
//1,創(chuàng)建線程
self.tread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[self.tread start];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 線程通信
[self performSelector:@selector(test1) onThread:self.tread withObject:nil waitUntilDone:YES];
}
-(void)test
{
NSLog(@"%s %@ ",__func__ ,[NSThread currentThread]);
//為了解決該任務(wù)執(zhí)行完畢棉浸。子線程就立馬銷毀問題锋喜,我們開啟runloop
//1 , 獲取子線程對應(yīng)的runloop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
//2 , 因?yàn)閞unloop必須要有一個timer 活著 source 保證運(yùn)行狀態(tài) 精居,所以我們可以添加一個timer 活著 source 事件
//方式 一:添加一個timer
// NSTimer *timer = [NSTimer timerWithTimeInterval:2.0f target:self selector:@selector(run) userInfo:nil repeats:YES];
// [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
//方式 二 : 添加一個source 搂鲫。端口事件
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//可選傍药。 10s 后退出runloop 。 配合方式 二 的
[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
NSLog(@"end");
}
-(void)test1
{
NSLog(@"%s %@ ",__func__ ,[NSThread currentThread]);
}
/* 打印結(jié)果魂仍,一個子線程拐辽。執(zhí)行了 test 又執(zhí)行了 test1
2017-11-14 00:23:37.152 RunloopDemo[1732:112273] -[ViewController test] <NSThread: 0x6000000766c0>{number = 3, name = (null)}
2017-11-14 00:23:38.929 RunloopDemo[1732:112273] -[ViewController test1] <NSThread: 0x6000000766c0>{number = 3, name = (null)}
2017-11-14 00:23:39.683 RunloopDemo[1732:112273] -[ViewController test1] <NSThread: 0x6000000766c0>{number = 3, name = (null)}
*/
如有不妥:請大神輕噴??
NSOperation簡單的入門
GCD入門