多執行緒(pthread,NSThread,GCD)

weixin_33976072發表於2015-09-27

1.什麼是執行緒?
2.什麼是程式?
3.什麼又是多執行緒?

程式就是指在系統中正在執行的應用程式.
每個程式之間是獨立的, 每個程式均執行在其專用且受保護的記憶體內.

執行緒是程式的一條執行路徑.
執行緒注意點:
1>執行緒執行任務是序列的. (所以才有了子執行緒的概念, 如果都放在一個執行緒中執行任務, 當下載大圖片的時候, 就什麼操作也做不了了. .....)

關於多執行緒:
一個程式中可以開啟多個執行緒, (一個應用程式中能夠開啟多個通道, 讓不同的任務進行執行, 而不會導致 阻塞).
這樣在同一時間內, 既不影響使用者的體驗, 也將大資料/耗時操作 得以執行.

關於多執行緒的原理:
1.同一時間,CPU只能處理一條執行緒, 只有一條執行緒在工作(執行)
2.多執行緒併發(同時)執行,其實是CPU快速的在多條執行緒之間排程(切換).
3.如果CPU排程執行緒的時間足夠快,就造成了多執行緒併發執行的假象.

思考:如果執行緒很多時,會出現什麼情況?
1>CPU會在多個執行緒之間排程, 累死了,消耗了大量的CPU資源
2>每條執行緒被排程執行的頻率降低( 會很卡的).

多執行緒的優缺點:
優點:
1>能適當提高程式的執行效率.
2>適當的提高資源利用率

缺點:
1>建立執行緒是有開銷的. 建立時間大約 90毫秒, 佔用一定記憶體資源
2>大量的開啟執行緒, 會降低程式的效能.
3>執行緒越多, CPU在排程執行緒上的開銷就越大(無故消耗資源)
4>程式設計更加複雜,執行緒之間的通訊, 以及資源共享等問題

多執行緒在iOS開發中的應用:

1>主執行緒: 和程式的啟動有關, 程式已啟動,預設開啟一個主執行迴圈, 內部有一個主執行緒.(UI執行緒)
2>主執行緒的主要作用:
顯示/重新整理UI介面 (一般都是一部請求資料, 會主執行緒重新整理UI)
處理UI事件(比如 點選, 拖拽,滾動等)
3>使用主執行緒需要注意的
不要將耗時操作放置在主執行緒執行. (阻塞主執行緒 , 嚴重影響UI的流暢度)

692194-5fdba3abdd10f955.png
Snip20150923_2.png

pthread(純C語言, 比較難使用,這裡只進行簡單方法的說明)

// 點選螢幕增加子執行緒 ,來執行耗時的 方法.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 將耗時操作放在子執行緒中執行
      pthread_t  thread;
    /*
    第一個的引數:執行緒的代號(就是代指執行緒)
     第二個引數:執行緒的屬性
     第三個引數:只想指向函式的指標,就是執行緒需要執行的方法
     第四個引數: 就是指向函式的指標 傳遞的引數。
    */
    pthread_create(&thread, NULL,&demo , "lxl");
    
  // 用於驗證 ,主執行緒繼續執行操作  (沒有阻塞主執行緒)
    NSLog(@"劉小龍,你好,%@",[NSThread currentThread]);
}
// 設定耗時的方法 ( 只想函式的指標)
void *demo(void *parma){

    // 列印傳入的引數, 以及當前的執行緒, 是1則是主執行緒,其他都不是
    NSLog(@"%s,%@",parma,[NSThread currentThread]);
    
    // 一些耗時的操作
    for (int i = 0; i < 99999; i++) {
        // NSLog是一個非常耗時的操作
        // 上架之前  全部清掉.
        NSLog(@"%i",i);
    }
   return NULL;
}

NSThread(不長使用, 需要自己手動管理它的生命週期) [現在使用它只有一個目的 就是獲取當先所處的執行緒 [NSThread currentThread] ]

建立和啟動執行緒
1>一個NSThread物件就代表一個執行緒.
2>建立.啟動執行緒 (直接設定執行緒的執行者, 和執行緒執行的方法, 傳入的引數)
- initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#>
- start開啟執行緒
3>常用的與主執行緒相關的方法:
+ mainThread獲得主執行緒
- isMainThread是否是主執行緒
+ isMainThread 是否為主執行緒
+ currentThread獲取當前的執行緒
- setName:設定執行緒的名字,方便管理
4> 建立一個直接啟動的執行緒(不能設定名字)
+ detachNewThreadSelector: toTarget: withObject:
5>隱式建立並啟動執行緒(一般我都不會這麼用,不好管理)
[self performSelectorInBackground:@selector(run) withObject:nil];

執行緒的狀態

692194-5f30b7190c80938b.png

1>啟動執行緒
- start 進入就緒狀態--->執行狀態. 執行緒任務執行完畢,自動進入死亡狀態
2>阻塞(暫停)執行緒
+ sleepUntilDate: // 執行緒休眠到什麼時候
+ sleepForTimerInterval:// 休眠幾秒
阻塞執行緒
3>強制停止執行緒
+ exit 進入死亡狀態 ---一旦執行緒停止(死亡), 就不能再次開啟任務, 需要重新定義.
#############
1.建立子執行緒:新建
2.就緒:啟動子執行緒
3.執行:排程子執行緒
4.阻塞:呼叫了sleep/等待同步鎖 @synchronized(id){}
5.死亡:執行緒執行完畢,或者異常退出/強制退出
注意: 對於執行緒物件,建立的時候被加到了 可排程執行緒池中, 待排程使用。 如果呼叫了sleep/同步鎖,就會被移除排程池,當sleep/同步鎖到時後,會再被引入排程池,做就緒準備

互斥鎖,與執行緒之間的搶奪, 已經在上文講出

執行緒之間的通訊

1>執行緒之間不是孤立存在的, 多個執行緒之間需要經常進行通訊.
2> 資料傳遞, 任務傳遞

3>常用方法[一般都是控制器自己呼叫的]
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)obj waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)the withObject:(id)arg waitUntilDone:(BOOL)wait

GCD (Grand Central Dispatch),"牛逼的中樞排程器"

1>純C語言,提供了很強的函式
2>優勢:
GCD是蘋果公司為多核的並行運算提出的解決方案
GCD會自動利用更多的CPU核心
GCD會自動管理執行緒的生命關係(建立執行緒,排程任務,銷燬執行緒)
程式設計師只需要告訴GCD想要執行什麼任務,不需要管理執行緒
3>任務與佇列
任務:執行什麼操作
佇列:用來存放任務
4>GCD的使用就2個步驟
定製任務
確定想做的事情
5>將任務新增到佇列中
執行緒---->佇列----->任務
任務加入佇列, 佇列放置線上程中執行
GCD會自動將佇列中的任務取出,放到對應的執行緒中執行
任務的取出遵循佇列的FIFO原則:先進先出,後進後出.

########任務的執行
GCD常用的2個用來執行任務的常用函式
1>
1.同步的方式執行任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:佇列 block:任務
2.非同步的方式執行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

2>同步和非同步的區別
同步:只能在當前的執行緒中執行任務,不具備開啟新執行緒的能力
非同步:可以在新的執行緒中執行任務,具備開啟新執行緒的能力

3>執行任務
GCD中還有個來執行任務的函式:(新增依賴)
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
前面的佇列執行完任務後,才能執行後面的任務 ,這裡的佇列不能傳全域性併發佇列, 必須傳一個固定的佇列.

4>GCD的佇列可以分為2個型別:
併發佇列(Concurrent Dispatch Queue)
系統建立多條執行緒來併發(同時)執行佇列中的任務, 但開啟幾條,由系統決定, 這個佇列只有在非同步函式中有效果
序列佇列(Serial Dispatch Queue)
讓任務一個個執行 (一個任務執行完畢後,在執行下一個任務)

########同步/非同步;併發/序列
同步/非同步:能不能開啟新的執行緒
同步:只是在當前執行緒(一般都是指主執行緒)中執行任務,不具備開啟新執行緒的能力.
非同步:可以在新的執行緒中執行任務,具備開啟新執行緒的能力

  併發/序列:任務的執行方式
  併發:允許多個任務併發(同時)執行
  序列:一個任務執行完畢後,再執行下一個任務

5>併發佇列
5.1dispatch_queue_create函式建立佇列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
lable: 佇列名稱
attr: 佇列的型別
簡單的建立併發佇列:
dispatch_queue_t queue = dispatch_queue_create("123",DISPATCH_QUEUE_CONCURRENT)

5.2全域性併發佇列
dispatch_get_global_queue函式獲得全域性的併發佇列
函式的書寫
dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority ,// 佇列的優先順序 unsigned long flags //這個只是一個標誌, 一般不用 傳0)優先順序預設也是0

6>序列佇列
6.1 dispatch_queue_create函式建立序列佇列
建立序列佇列(佇列型別傳遞NULL 或者 DISPATCH_QUEUE_SERIAL)
簡單建立序列佇列
dispatch_queue_t queue = dispatch_queue_create("123",NULL)
6.2 使用主佇列 (跟主執行緒相關聯的佇列)
主佇列是GCD自帶的一種特殊的序列佇列
放在主佇列的任務,都會放在主執行緒中執行
使用dispatch_get_main_queue()獲得主佇列

692194-cb7342e3b1c8b735.png
Snip20150924_4.png

7>執行緒之間的通訊
子執行緒執行耗時操縱, 會主執行緒重新整理UI控制元件

dispatch_async(
dispatch_get_global_queue(0,0),^{
// 耗時的非同步操作
.....
 #warning 必須使用非同步呼叫主佇列.  同步的話,會造成阻塞主執行緒(因為有矛盾, 主執行緒不想讓執行block, 主佇列又想, 這樣很矛盾的)
dispatch_async(dispatch_get_main_queue(),^{
// 返回主執行緒,執行UI重新整理操作
});
});

8>延遲執行
常見的幾種延遲執行的方法:
8.1 呼叫NSObject的方法

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

8.2使用GCD函式

dispatch_after(dispatch_time(DISPATCH_TIME_NOW , (int64_t)(2.0 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
// 2秒後執行這裡的程式碼......
});

8.3使用NSTimer

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

/##############特殊應用##############/
8.4一次性程式碼
使用dispatch_once函式能保證某段程式碼在程式執行過程中只被執行一次

static dispatch_once_t oneToken;
dispatch_once(&onceToken,^{
    // 只執行一次的程式碼(在這裡預設是執行緒安全的)
});

8.5快速迭代
使用dispatch_apply函式能進行快速迭代遍歷

dispatch_apply(10,dispatch_get_global_queue(0,0),^(size_t index){
// 執行10次程式碼, index順序不確定
.....
})

8.6佇列組
有點等同於依賴的關係,
但是對於某些特殊要求還是需要建立佇列組來完成
比如有這麼一個需求
1>首先:分別非同步執行2個耗時操作
2>其次:等2個非同步操作都執行完畢後,再回到主執行緒執行操作

dispatch_group_t group = dispatch_group_creat();
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0),
^{
    //
執行1個耗時的非同步操作
});
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0),
^{
    //
執行1個耗時的非同步操作
});

dispatch_group_notify(group,
dispatch_get_main_queue(),
^{
    //
等前面的非同步操作都執行完畢後,回到主執行緒...
});,,

相關文章