多線程

多線程原理:

  • 同一時間CPU只能處理一條線程, 只有一條線程在工作
  • 多線程并發執行,起始是CPU在各個線程之間快速調度的結果
  • 由于CPU調度線程速度非常快,所以就造成了多線程并發的假象

一般情況下耗時操作放在子線程里面,多線程也正是解決耗時操作,防止卡住主線程產生的。

主線程

  • 程序已啟動就自動創建的線程就是主線程
  • 作用一般就是相應用戶點擊事件,刷新UI等等

多線程的實現方案

Paste_Image.png
  • NSThread
@Parmark 第一中創建方法
    //創建線程
    //在內存中開辟空間
    NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"thread"];
    thread.name = @"my-thread";
    //啟動線程  并且系統會把這個線程放到可調度程序池里面 為了方便CPU來回調用
    [thread start];

//任務執行完畢后 會自動銷毀線程
- (void)run:(NSString *)param
{
    //處理耗時操作
}

@Parmark 第二中創建方法

[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"thread"];

- (void)run:(NSString *)param
{
    //處理耗時操作
}

@Parmark 第三中創建方法

[self performSelectorInBackground:@selector(run:) withObject:@"thread"];

- (void)run:(NSString *)param
{
    //處理耗時操作
}


 //卡住線程睡兩秒 控制線程進入阻塞狀態
[NSThread sleepForTimeInterval:2.0];


//退出線程(強制性的)
[NSThread exit];

線程安全

  • 資源共享:一塊資源 被多個線程共享 也就是多個線程訪問同一塊資源

隱患:

Paste_Image.png

解決引號 -- > 加把互斥鎖

Paste_Image.png

代碼:

###沒加互斥鎖的代碼

    self.ticketCount = 100;//100張票
    
    self.thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread1.name = @"售票員1";
    
    self.thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread2.name = @"售票員2";
    
    self.thread3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread3.name = @"售票員3";

- (void)saleTicket
{
    while (1) {
        //先取出總數
        NSInteger count = self.ticketCount;
        if (count > 0) {
            
            self.ticketCount = count - 1;
            
            NSLog(@"%@賣了一張票 -- 還剩下%ld張票",[NSThread currentThread].name,_ticketCount);
        }else{
            NSLog(@"票已經賣完了");
            break;
        }
    }
}
###加互斥鎖的代碼
- (void)saleTicket
{
    //用同一把鎖 可以記錄線程的狀態
    while (1) {
        
        @synchronized (self) {//加鎖
            
            //先取出總數
            NSInteger count = self.ticketCount;
            if (count > 0) {
                
                self.ticketCount = count - 1;
                
                NSLog(@"%@賣了一張票 -- 還剩下%ld張票",[NSThread currentThread].name,_ticketCount);
            }else{
                NSLog(@"票已經賣完了");
                break;
            }
        }
        
    }
}

線程間的通訊


//開辟一個子線程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelectorInBackground:@selector(downLoad) withObject:nil];
}


//子線程要做的事
- (void)downLoad
{
    NSURL * url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/5ab5c9ea15ce36d358d27ee43ef33a87e850b114.jpg"];
    
    //下載圖片
    NSData * data = [NSData dataWithContentsOfURL:url];
    
    //生成圖片
    UIImage * image = [UIImage imageWithData:data];
    
    //回到主線程刷新UI
    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
    
//    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

}

GCD的基本使用

  • 任務: 執行操作
  • 隊列: 存放任務
    • 隊列的類型
    • 并發隊列 (只有在)dispatch_async下才有效
    • 串行隊列

同步 - 異步:主要影響是能不能開新線程

  • 同步只能在當前線程執行任務,不能開啟新線程
  • 異步可以在新的線程中執行任務,能開啟新線程

串行 - 并發:主要影響任務的執行方式

  • 串行:一個任務執行完畢 執行下一個任務
  • 并發:多個任務同時執行

使用步驟

  • 定制任務:確定想要做的事情
  • 將任務添加到隊列中
    • GCD會自動將隊列中的任務取出來,放到對應的線程中執行
    • 任務的去除遵循:先進先出 后進后出 的原則

代碼:


//異步線程
    dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
    //同步線程
    dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

###異步函數+并發隊列 (可以開啟多條線程 并且可以同時執行)
//創建一個并發隊列  DISPATCH_QUEUE_CONCURRENT:隊列類型
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_CONCURRENT);
    
    //將任務加入隊列
    dispatch_async(queue, ^{
       
        NSLog(@"%@",[NSThread currentThread]);
        
    });

#@pargam 或者這種寫法

//獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //將任務加入隊列
    dispatch_async(queue, ^{
       
        NSLog(@"%@",[NSThread currentThread]);
        
    });


###同步函數+并發隊列 (不會開啟新的線程)

//獲得全局的并發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //同步函數
    dispatch_sync(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });


###異步函數+串行對列 (可以開線程 但是不能同時執行)
//串行隊列 沒有全局的 只能手動創建
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });

###同步函數+串行對列 (不可以開線程 )

 //串行隊列 沒有全局的 只能手動創建
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });

主隊列

  • GCD自帶的一種特殊的串行隊列
  • 放到主隊列的任務都會,在主線程中執行
  • 使用dispatch_get_main_queue()獲取主隊列

###主隊列+異步函數(只會在主線程中執行任務)

dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
    });

###主隊列+同步函數(線程沖突 不會執行任何操作)

dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
    });

各種隊列的執行效果

Paste_Image.png

GCD的線程之間通訊


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        NSURL * url = [NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1479864304&di=99dcf40127f2dc4273536f73d0951638&src=http://d.hiphotos.baidu.com/image/pic/item/2e2eb9389b504fc2065e2bd2e1dde71191ef6de0.jpg"];
        NSData * data = [NSData dataWithContentsOfURL:url];
        
        UIImage * image = [UIImage imageWithData:data];
        
        //回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
           
            self.imageView.image = image;
            
        });
        
    });

GCD中還有另外一個執行任務的函數

//在它前面的函數執行完畢才執行它的任務,在它后面的函數在它執行完畢后才開始執行
dispatch_barrier_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

iOS延時執行的函數

//延時兩秒執行run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];


dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //執行的操作
        
    });

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];


static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //一次性函數
        //此函數只執行一次
    });

GCD隊列組

  • 用隊列組下載多張圖片并且合成一張圖片

//創建一個隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //創建一個隊列組
    dispatch_group_t group = dispatch_group_create();
    
    //1.下載圖片1
    dispatch_group_async(group, queue, ^{
        
        NSURL * url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/h%3D200/sign=4241e02c86025aafcc3279cbcbecab8d/562c11dfa9ec8a13f075f10cf303918fa1ecc0eb.jpg"];
        NSData * data = [NSData dataWithContentsOfURL:url];
        
        UIImage * image = [UIImage imageWithData:data];
        
        self.image1 = image;
        
        
    });
    //2.下載圖片2
    dispatch_group_async(group, queue, ^{
        
        
        NSURL * url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/pic/item/8cb1cb134954092359d94e479758d109b3de4952.jpg"];
        NSData * data = [NSData dataWithContentsOfURL:url];
        
        UIImage * image = [UIImage imageWithData:data];
        
        self.image2 = image;
        
        
    });
    //3.將圖片1和圖片2合成一張新的圖片
    dispatch_group_notify(group, queue, ^{
        //能保證組里面的任務都完成了
        //能來到這里說明前兩張圖片一定下載完了
        
        //開啟圖形上下文
        UIGraphicsBeginImageContext(CGSizeMake(336, 440));
        
        //繪制圖片
        [self.image1 drawInRect:CGRectMake(0, 0, 168, 220)];
        [self.image2 drawInRect:CGRectMake(168, 0, 168, 220)];
        
        //獲取上下文的圖片
        UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
        
        //結束上下文
        UIGraphicsEndImageContext();
        
        //回到主線程顯示圖片
        dispatch_async(dispatch_get_main_queue(), ^{
           
            self.imageView.image = image;
            
        });
        
        
    });
    //4.將合成后的圖片顯示出來

GCD實現單利

###第一種方式

static Person * _person;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone: zone];
    });
    return _instance;
}

+ (instancetype)defaultManger
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        _person = [[self alloc]init];
        
    });
    return _person;
}


//記得遵守NSCopying協議
//實現此方法為了保證copy的時候  訪問的是同一個對象
- (id)copyWithZone:(NSZone *)zone
{
    return _person;
}


###第二種方式

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized (self) {//加鎖 防止多線程訪問出問題
        if (_instance == nil)
        {
            _instance = [self allocWithZone:zone];
        }
    }
    return _instance;
}

+ (instancetype)sharedInstance
{
    @synchronized (self) {//加鎖 防止多線程訪問出問題
        if (_instance == nil)
        {
            _instance = [[self alloc]init];
        }
    }
    return _instance;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容