多線程1

常用操作

多行注釋: Command + option + /
獲取當前的時間:CACurrentMediaTime()
后臺運行程序:[self performSelectorInBackground:@selector(longOperation) withObject:nil];
創建子線程,讓longOperation方法在子線程異步執行
target-select:

多線程基礎

  1. 空的for循環不耗時
  2. 操作棧區內存空間不耗時,因為棧區的內存空間是連續的,不需要尋址
  3. 操作常量區內存空間不耗時,但是比操作棧區空間耗時一些,尋址只做一次
  4. 操作堆區的內存空間相對于棧區和常量區是耗時的,因為堆區的內存空間不是連續的,需要尋址
  5. I/O操作也是非常耗時的
  6. 耗時操作對UI影響:會卡死UI
  7. 如何解決耗時操作卡死UI的問題:使用多線程技術
  8. 多線程的核心思想: 就是把耗時操作放在后臺執行,避免耗時操作卡死UI
  9. 在實際開發中,網絡操作時非常耗時的,一般會把網絡操作(下載,上傳...)放在后臺執行
  10. 學習多線程就是為了學習網絡做準備的
    I/O操作:是把內存的數據輸出到外接設備(屏幕,磁盤)output,把外接設備的數據輸入到內存input

同步&異步

同步異步 是任務執行的兩種方式
同步:多個任務按序依次執行
異步:多個任務同時執行,就是異步執行(后臺執行就是異步執行)

進程&線程

進程:
在系統中 正在運行 的一個應用程序就是一個進程
通過 活動監視器 可以查看MAC系統中 正在運行 的所有應用程序
每個進程之間都是 獨立 的,均運行在其 專用受保護 的內存空間
兩個進程之間是無法通信的,迅雷無法幫助我們下載正在播放的音樂
進程可以類比成正在 正常運行 的公司
線程:
線程可以類比成公司中的員工
進程要想執行任務,必須要有線程,且每個進程至少有一個線程
線程是進程的 基本執行單元,進程中的所有任務都在線程中執行
程序啟動(進程開啟)會默認開啟一條線程
1個進程中可以有多個線程
多線程:
一個進程中可以開啟多條線程,多條線程可以同時執行不同的任務
進程-公司,線程-員工
多線程可以解決程序阻塞的問題
多線程可以提高程序的執行效率,給用戶良好的使用體驗
多線程執行原理:
單核CPU同一時間,CPU只能處理1個線程,只有1個線程在執行任務
多線程的同時執行:其實就是CPU在多線程之間快速切換(調度任務)
如果CPU調度線程的速度足夠快,就造成了多線程同時 執行 的假象
如果線程非常多,CPU會在多線線程之間不斷的調度任務,結果就是消耗了大量的CPU資源,CPU會趴下
每個線程調度的頻率會降低
線程的執行效率會下降
多線程的優缺點
實際開發中,能不用多線程就不用,主線程夠用
如果必須使用,就簡單使用
優點

  1. 能夠適當提高程序的執行效率
  2. 能適當提高CPU和內存的利用率
  3. 線程上的任務執行完成后,線程會自動銷毀,節省內存

缺點

  1. 開啟線程需要占用一定的內存空間,如果開啟的線程過多,會占用大量的CPU資源,降低程序的性能
    2.占用內存空間:默認情況下,子線程512K,主線程1M,PS:IOS8中,主線程521K
  2. 線程越多,CPU調度線程的開銷就越大
    時間開銷
    空間開銷
  3. 程序設計更加復雜:比如多線程之間的通信,多線程的數據共享

主線程:

  1. 程序一起動就會創建主線程,主線程會執行main函數,
  2. 一個程序運行后,默認會開啟1個線程,稱為主線程UI線程
  3. 主線程一般用來刷新UI界面,處理UI事件
  4. 處理UI事件:點擊 滾動 拖拽
  5. 主線程使用注意:
    別將耗時的操作放在主線程中
    耗時操作會卡主主線程,嚴重影響UI的流暢度,給用戶一種卡的壞體驗,影響UI交互質量

凡是跟UI相關的都是在主線程執行的(子線程創建就有,不創建就沒有)

pthread

pthread創建線程

/*
     創建一個線程
     
      參數1:子線程的ID(標識)
     在C語言中,一般帶`_t`/`_ref`標識數據類型
      參數2:子線程的一個屬性,一般傳入NULL
            NULL:表示空地址,一般在C語言中使用
            nil:表示空對象,一般在OC中使用
            其實,NULL和nil本質上沒有半點區別
      參數3:子線程需要執行的函數
        void *(*)(void *)表示指向函數的指針,即函數名:函數名就是表示函數地址;數組地址就是數組名或者數組第0個角標元素的地址
        void*:表示可以指向任何地址的指針,代表任意數據類型,類似于OC的id
     void *   (*)  (void *):
     返回值   函數名   函數參數
      參數4:子線程需要執行的函數的參數
     返回值:int;在很多C語言框架中,不是遵守非零即真,因為成功的結果只有一個,0是唯一的;失敗的原因有很多
     線程調試:查看方法執行的線程是否是主線程或者子線程
     {number = 1, name = main}:表示主線程
     {number = 3, name = (null)}:表示子線程,主要number != 1,就表示子線程
     提示:千萬不要糾結number到底等于幾,系統分配的
    __bridge:在c語言和oc語言混合開發時,需要做數據類型轉換,有時候需要使用橋接
     橋接作用:在做數據類型轉換時,告訴編譯器如何管理c語言的內存
     提示:在ARC環境下,編譯器在編譯時,不會自動管理C語言申請的內存空間
     提示:加上__bridge 表示告訴編譯器,c語言申請的內存也是自動管理的,因為大環境是ARC的
     MRC環境下,不需要使用__bridge,因為手動管理內存
     */
     NSLog(@"%@",[NSThread currentThread]);
    //參數1
    pthread_t ID;
    //參數4
    NSString *str = @"hello";
    //創建了一個子線程
   int result =  pthread_create(&ID, NULL, demo, (__bridge void *)(str));
    //判斷子線程創建是否成功
    if(result == 0){
        NSLog(@"創建子線程成功");
    }else{
        NSLog(@"創建子線程失敗");
    }
    


/**
 子線程執行的函數

 */
void *demo(void *param){
    
    NSString *str = (__bridge NSString *)(param);
    //currentThread:查看當前線程
    NSLog(@"demo %@ - %@",[NSThread currentThread],str);
    return NULL;
}

NSThread

創建線程的3種方式

//分類方法創建子線程
//不可以拿到線程對象
//不需要手動啟動線程
-(void)threadDemo3{
    //方便所有繼承自NSObject的對象,可以直接調用線程的方法(swift里面沒有這個分類,也沒有GCD)
    [self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
}
//構造方法創建子線程
//不可以拿到線程對象
//不需要手動啟動線程
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self threadDemo2];
}
-(void)threadDemo2{
    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
}
//構造方法創建子線程
//可以拿到線程對象
//需要手動啟動線程
-(void)threadDemo1{
    //創建線程對象
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
    //啟動線程
    [thread start];
}
-(void)demo:(id)param{
    //子線程執行的方法
    NSLog(@"%@-%@",param,[NSThread currentThread]);
}

target和selector的關系

  • 執行哪個對象的哪個方法
  • 需求:執行Person對象的run方法
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"alloc"];

線程生命周期/線程狀態

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"喜大普奔,神舟十一");
   
    //注意:千萬不要在主線中,使用這個方法,會使主線程死亡
     //[NSThread exit];
    [self threadDemo];
}
-(void)threadDemo{
    //提示:程序員只能夠做新建和就緒,其他的都由系統來處理
    
    //創建線程對象:新建狀態
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //啟動線程:就緒狀態(把線程對象添加到可調度線程池,等待被CPU調度執行)
    [thread start];
    
}
-(void)demo{
    //NSLog(@"%@",[NSThread currentThread]);
    for (NSInteger i = 0; i < 5; i++) {
        //線程每次執行到這里就休眠1秒鐘:for循環,每循環一次就休息1秒鐘
        //sleepForTimeInterval:使當前的線程休眠到指定時長
        //sleepForTimeInterval:使用場景就是模擬網絡延遲操作,僅僅是模擬,開發中不會使用的
        [NSThread sleepForTimeInterval:1.0];
        
        NSLog(@"%zd - %@",i,[NSThread currentThread]);
        if(i == 2){
            //sleepUntilDate:使當前線程休眠到指定日期
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
            NSLog(@"藍瘦,香菇");
        }
        if(i == 3){
            //使當前線程強制死亡
            [NSThread exit];
        }
    }
}

線程屬性

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"主:%tu",[NSThread currentThread].stackSize/1024);

    //新建線程
   NSThread *thread =  [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //設置線程對象的name屬性,標識一個唯一的線程對象,方便跟蹤
    thread.name = @"t1";
    //設置線程對象的優先級:浮點數0.0-1.0,最高是1.0,默認是0.5
    //線程優先級不能決定線程執行的先后順序,只能決定某個線程有更多機會被CPU先調度執行完,概率事件
    //注意:實際開發中,千萬不要設置優先級,或者服務器質量,會出現意想不到的問題;使用默認的,讓系統自己來處理
    //thread.threadPriority = 1.0;
    //threadPriority:在目前即將被廢棄,使用qualityOfService替代
    thread.qualityOfService = NSQualityOfServiceUserInteractive;
    
    
    //就緒狀態
    [thread start];
    //新建線程
    NSThread *thread2 =  [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    thread2.name = @"t2";
    thread2.threadPriority = 0.1;
    //就緒狀態
    [thread2 start];
}
-(void)demo{
    //stackSize:線程占用內存空間大小
    NSLog(@"子:%tu",[NSThread currentThread].stackSize/1024);
    
    
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"%zd -- %@",i,[NSThread currentThread]);
    }
    /*模擬崩潰:演示name屬性
    NSMutableArray *arrM = [NSMutableArray array];
    NSObject *obj = nil;
    [arrM addObject:obj];
     */
    
    
}

資源共享-線程安全

共享資源
資源:一個全局對象,一個全局變量,一個文件
共享:可以被多個對象訪問
共享資源:可以被多個對象訪問的資源,比如全局對象,變量,文件
在多線程的環境下,共享的資源,可能被多個線程共享,也就是多個線程可能會操作同一塊資源

@interface ViewController ()

//總票數:共享資源
@property (assign,nonatomic) NSInteger tickets;

@end
//需求驅動開發:沒有需求,無從開發
//需求:開發買票系統
//先要有開發邏輯,后有開發代碼
//開發邏輯:先干什么,后干什么
//分析需求,得到發的邏輯
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化總票數
    self.tickets = 20;
    
    
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //[self sellTickers];
    //售票口1
   NSThread *thread1 =  [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
    thread1.name = @"t1";
    [thread1 start];
    //售票口1
    NSThread * thread2 =  [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
    thread2.name = @"t2";
    [thread2 start];
}
//賣票主方法
-(void)sellTickers{
    
    while (YES) {
        //互斥鎖/同步鎖:使用了線程同步技術
        //特點:可以保證被鎖定的代碼,同一時間只有一個線程可以訪問
        //self:表示互斥鎖的參數,互斥鎖的參數,又叫做鎖對象
        //鎖對象:任何繼承自NSObject的對象,都可以作為互斥鎖的參數,內部有把鎖,默認是開啟的
        //鎖對象必須是全局的對象;self是最方便獲取的全局的鎖對象
        //局部鎖對象是鎖不住的,因為每次線程進來之前會新建一把鎖
        //提示:加鎖的事情,不能再客戶端操作;是服務器加鎖的,多線程資源共享,絕大多數是在服務器發生;
        //提示:加鎖是犧牲了性能,保證了安全,客戶端的性能不能輕易犧牲
        
        //創建一個局部的鎖對象
        NSObject *obj = [[NSObject alloc] init];
        @synchronized (self) {
            //判斷是否有余票
            if(self.tickets > 0 ){
                //模擬網絡延遲:沒有實際意義,僅僅是模擬延遲而已,可以忽略
                //[NSThread sleepForTimeInterval:1.0];
                //如果有余票,賣一張
                self.tickets = self.tickets - 1;
                //提醒余票
                NSLog(@"%zd--%@",self.tickets,[NSThread currentThread]);
                
            }else{
                //如果沒有余票,提醒用戶無票
                NSLog(@"無票了");
                break;
            }

        }
    }
    
}

原子屬性

@interface ViewController ()

//非原子屬性
@property (nonatomic,strong) NSObject *obj1;
//原子屬性
@property (strong) NSObject *obj2;//一旦重寫了getter和setter方法,系統不會自動生成帶下劃線的成員變量
//需要自己合成帶下劃線的成員變量

@end
/*
 原子屬性:單寫多讀
 單寫多讀:同一時間只有一個線程可以訪問setter方法,但是可以有多個線程訪問getter方法
 注意:原子屬性的setter方法是線程安全的,getter方法是線程非安全的
 setter方法內部有自旋鎖
 自旋鎖:看不見的,由系統封裝的
    可以保證被鎖定的代碼,同一時間只能有一個線程可以訪問
 一旦外面的線程,發現代碼被自旋鎖鎖定,外面的線程會以死循環的方式等待開鎖
 互斥鎖:
    可以保證鎖定的代碼,同一時間只能有一個線程可以訪問
    一旦外面的線程,發現代碼被互斥鎖鎖定,外面的線程就會進入就緒狀態,
 */

@implementation ViewController

@synthesize obj2 = _obj2;
-(void)setObj2:(NSObject *)obj2{
    //由于自旋鎖看不見,所以可以使用互斥鎖替換,演示單寫多讀
    @synchronized (self) {
         _obj2 = obj2;
    }
   
}
-(NSObject *)obj2{
    return _obj2;
}

異步下載網絡圖片

@interface ViewController ()

//根視圖
@property (strong,nonatomic) UIScrollView *scrollView;

//圖片子視圖
@property (nonatomic,weak) UIImageView *imgView;

@end
//需求:異步下載網絡圖片,圖片可以滾動,滾動視圖要是根視圖
//分析需求:下載是耗時的操作,需要在子線程異步執行
//準備控件的工作UIImageView/UIScrollView(根視圖)
//
@implementation ViewController
/*
 loadView:優先于viewDidLoad
 loadView:當self.view == nil 時調用
 loadView:不需要調用super
 */
-(void)loadView{
    //創建根視圖
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    //把根視圖替換成scrollView
    self.view = self.scrollView;
    self.scrollView.backgroundColor = [UIColor redColor];
    
    //創建圖片子視圖
    UIImageView *imgView = [[UIImageView alloc] init];
    [self.view addSubview:imgView];
    //給屬性賦值:一定不能少
    self.imgView = imgView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //[self laodImageData];
    [self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}

//在子線程下載圖片,在主線程更新UI,是線程間通信的一種
//線程間通信:一個線程把他執行的結果,傳遞到另外的一個線程

//下載圖片的主方法
-(void)loadImageData{
    //URL
    NSURL *url = [NSURL URLWithString:@"http://img05.tooopen.com/images/20150202/sy_80219211654.jpg"];
    //發送網絡請求,獲取圖片二進制數據,是個耗時的操作
    NSData *data = [NSData dataWithContentsOfURL:url];
    //image就是子線程執行的結果,需要傳遞到主線程
    UIImage *image = [UIImage imageWithData:data];
    
    //下載完成后,通知主線程刷新UI
    //waitUntilDone:是否等到updateUI執行完,再執行后面的代碼,一般傳入NO
    [self performSelectorOnMainThread:@selector(updataUI:) withObject:image waitUntilDone:NO];
    NSLog(@"后面的代碼");
}
-(void)updataUI:(UIImage *)image{
    //下載完成后,刷新UI
    self.imgView.image = image;
    [self.imgView sizeToFit];
    self.scrollView.contentSize = image.size;
}

異步下載

info.plist中添加代碼,允許請求網絡
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • 一、基本概念 01 - 進程 進程是指在系統中正在運行的一個應用程序。 每個進程之間是獨立的,每個進程均運行在其專...
    麥穗0615閱讀 358評論 0 0
  • Object C中創建線程的方法是什么?如果在主線程中執行代碼,方法是什么?如果想延時執行代碼、方法又是什么? 1...
    AlanGe閱讀 1,760評論 0 17
  • 進程 什么是進程?進程是指在系統中正在運行的一個應用程序每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存...
    海強_hq閱讀 507評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,610評論 25 708
  • “資深員工”是個坑,好多人還在坑里,好多人已被埋葬! 每回有人向圍觀哥介紹:這是我們公司的資深員工、資深老員工.....
    圍觀哥閱讀 721評論 0 0