多線程03---NSThread

每個iOS應用程序都有個專門用來更新顯示UI界面、處理用戶的觸摸事件的主線程,因此不能將其他太耗時的操作放在主線程中執行,不然會造成主線程堵塞(出現卡機現象),帶來極壞的用戶體驗。一般的解決方案就是將那些耗時的操作放到另外一個線程中去執行,多線程編程是防止主線程堵塞,增加運行效率的最佳方法:

1.iOS支持多個層次的多線程編程,層次越高的抽象程度越高,使用也越方便,也是蘋果最推薦使用的方法。
2.NSThread :是蘋果提供的三種方法裏面相對輕量級的,但需要管理線程的生命週期、同步、加鎖問題,這會導致一定的性能開銷。

1.NSThread的初始化

1.初始化:

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;  

參數解析:

  • selector :線程執行的方法,這個selector最多隻能接收一個參數
  • target :selector消息發送的對象
  • argument : 傳給selector的唯一參數,也可以是nil

簡潔的初始化方法:

  • 優點:創建線程後自動啓動線程
  • 缺點:無法對線程進行更詳細的設置
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; 
[self performSelectorInBackground:@selector(run) withObject:nil];

2.線程的啓動

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 線程一啓動,就會在線程thread中執行self的run方法
[thread start];

3.其他基本用法

//主線程相關用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否爲主線程
+ (BOOL)isMainThread; // 是否爲主線程

//獲得當前線程
NSThread *current = [NSThread currentThread];

//線程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;

4.線程的狀態

這裏寫圖片描述

啓動線程:

// 進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態
- (void)start; 

阻塞(暫停)線程

// 進入阻塞狀態
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

強制停止線程

// 進入死亡狀態
+ (void)exit;

注意:一旦線程停止(死亡)了,就不能再次開啓任務

5.多線程的安全隱患

資源共享:

  • 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
  • 比如多個線程訪問同一個對象、同一個變量、同一個文件
  • 當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題

這裏寫圖片描述
我們可以發現由於多線程的原因,錢變少了,只有500了

安全隱患分析:
這裏寫圖片描述
多個線程同時操作一塊資源,導致資源更新錯誤,本來應該是19的,結果卻是18.

解決方法:互斥鎖
這裏寫圖片描述
互斥鎖很容易理解,當一個線程訪問資源的時候,就禁止其他線程訪問資源,只有等到該資源被釋放纔可以。

互斥鎖使用格式
@synchronized(鎖對象) { // 需要鎖定的代碼 }
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的

#import "ViewController.h"

@interface ViewController ()

/** 售票員1 */
@property (nonatomic, strong) NSThread *thread1;
/** 售票員2 */
@property (nonatomic, strong) NSThread *thread2;
/** 售票員3 */
@property (nonatomic, strong) NSThread *thread3;
/** 剩餘票數 */
@property (nonatomic, assign) NSInteger count;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 0.初始化剩餘票數
    self.count = 100;

    // 1.創建3個售票員
    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(sell) object:nil];
    thread1.name = @"A";
    self.thread1 = thread1;

    NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(sell) object:nil];
    thread2.name = @"B";
    self.thread2 = thread2;
    NSThread *thread3 = [[NSThread alloc]initWithTarget:self selector:@selector(sell) object:nil];
    thread3.name = @"C";
    self.thread3 = thread3;

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 1.讓3個售票員同時開始賣票
    [self.thread3 start];
    [self.thread2 start];
    [self.thread1 start];
}
/******************************************************************************************
只要被@synchronized的{}包裹起來的代碼, 同一時刻就只能被一個線程執行
注意:
1. 只要枷鎖就會消耗性能
2. 加鎖必須傳遞一個對象, 作爲鎖
3. 如果想真正的鎖住代碼, 那麼多個線程必須使用同一把鎖才行
4. 加鎖的時候儘量縮小範圍, 因爲範圍越大性能就越低
5. @synchronized(self.obj)// 鎖住
6. 在開發中, 如果要加鎖, 一般情況都使用self
******************************************************************************************/
-(void)sell
{
    while (true) {
        @synchronized(self)
        {
            if (self.count > 0) {
                NSLog(@"count: %ld  售票員:%@",self.count,[NSThread currentThread].name);
                self.count --;
            } else {
                NSLog(@"賣完");
                [NSThread exit];
            }
        }
    }
}
@end

互斥鎖的優缺點
優點:能有效防止因多線程搶奪資源造成的數據安全問題
缺點:需要消耗大量的CPU資源

互斥鎖的使用前提:多條線程搶奪同一塊資源

相關專業術語:線程同步
線程同步的意思是:多條線程在同一條線上執行(按順序地執行任務)
互斥鎖,就是使用了線程同步技術

6.原子和非原子屬性

1.OC在定義屬性時有nonatomic和atomic兩種選擇

  • atomic:原子屬性,爲setter方法加鎖(默認就是atomic)
  • nonatomic:非原子屬性,不會爲setter方法加鎖

2.nonatomic和atomic對比

  • atomic:線程安全,需要消耗大量的資源
  • nonatomic:非線程安全,適合內存小的移動設備

3.iOS開發的建議

  1. 所有屬性都聲明爲nonatomic
  2. 儘量避免多線程搶奪同一塊資源
  3. 儘量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力

注意點: atomic系統自動給我們添加的鎖不是互斥鎖/ 自旋鎖
互斥鎖和自旋鎖共同點:

  • 都能夠保證多線程在同一時候, 只能有一個線程操作鎖定的代碼

互斥鎖和自旋鎖不同點:

  • 如果是互斥鎖, 假如現在被鎖住了, 那麼後面來得線程就會進入”休眠”狀態, 直到解鎖之後, 又會喚醒線程繼續執行
  • 如果是自旋鎖, 假如現在被鎖住了, 那麼後面來得線程不會進入休眠狀態, 會一直傻傻的等待, 直到解鎖之後立刻執行
  • 自旋鎖更適合做一些較短的操作

7.線程間通信

1.什麼叫做線程間通信

  • 在1個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信

2.線程間通信的體現:

  • 1個線程傳遞數據給另1個線程
  • 在1個線程中執行完特定任務後,轉到另1個線程繼續執行任務

3.線程間通信常用方法:

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
  • 第一個參數:aSelector表示創建的線程的方法
  • 第二個參數:thr表示傳入的線程
  • 第三個參數:arg表示傳入線程的方法的參數
  • 第四個參數:waitUntilDone的含義:
    1. 如果傳入的是YES: 那麼會等到主線程中的方法執行完畢, 纔會繼續執行下面其他行的代碼
    2. 如果傳入的是NO: 那麼不用等到主線程中的方法執行完畢, 就可以繼續執行下面其他行的低嗎
//表示跳轉到主線程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

4.線程通信實例:
在子線程中下載圖片,下載完畢更新主線程的UI

1.在storyboard中設置UIImageView的約束
這裏寫圖片描述

2.寫代碼

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (nonatomic,strong) NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadPic) object:nil];
    thread.name = @"downloadImage";
    self.thread = thread;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.thread start];
}

-(void)downloadPic
{
    NSString *string = @"http://avatar.csdn.net/7/4/5/1_misakahina.jpg";
    NSURL *url = [NSURL URLWithString:string];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    // waitUntilDone的含義:
    //如果傳入的是YES: 那麼會等到主線程中的方法執行完畢, 纔會繼續執行下面其他行的代碼
    //如果傳入的是NO: 那麼不用等到主線程中的方法執行完畢, 就可以繼續執行下面其他行的低嗎

    //[self performSelectorOnMainThread:@selector(showPicture:) withObject:image waitUntilDone:YES];
    //[self performSelector:@selector(showPicture:)  onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
    [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

- (void)showPicture:(UIImage*)image
{
    self.imageView.image = image;
}
@end
發佈了190 篇原創文章 · 獲贊 12 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章