1. 程式人生 > >iOS-執行緒&&程序的深入理解

iOS-執行緒&&程序的深入理解

程序基本概念

  • 程序就是一個正在執行的一個應用程式;
  • 每一個進度都是獨立的,每一個程序均在專門且手保護的記憶體空間內;
  • iOS是怎麼管理自己的記憶體的,見部落格:部落格地址
  • 在Linux系統中,想要新開啟一個程序是一件非常簡單的事情只需要一句話:fork(),在fork()之後就會包含兩個程序,此時可以根據返回的PID來判斷是子程序還是父程序;
  • iOS中是一個非常封閉的系統,每一個App(一個程序)都有自己獨特的記憶體和磁碟空間,別的App(程序)是不允許訪問的(越獄不在討論範圍);
  • 所以,在iPhone中下載了兩種音樂播放器,下載的音樂歌曲卻是不能共享的!跟安卓完全不同!——有好有壞吧!
  • 如果想學習程序方面的知識,我覺得還是看看Linux對程序的描述,比如程序間的通訊(管道、記憶體共享、實用訊號量防止資源搶奪等)。

執行緒基本概念

- 執行緒是CPU排程的最小單元;
- 執行緒的作用:執行app的程式碼;
- 一個程序(App)至少有一個執行緒,這個程序叫做主執行緒;

執行緒和程序的關係

  • 程序和應用程式的關係:程序為應用程式開闢記憶體空間;
  • 執行緒和應用程式的關係:執行緒執行應用程式的程式碼;
  • 程序和執行緒之間的關係:程序是由執行緒組成的、一個程序理論上可以有很多個執行緒、但至少有一個主執行緒;

執行緒

在iOS中程序相關的操作並不是很多,常見的就App之間相互呼叫,蘋果公司將這些操作都封裝在了UIApplcation這個類中了。

為什麼要使用多執行緒

CPU -> 程序 -> 執行緒;

如果是在Linux系統中,在討論為什麼在使用多程序時,是針對多程序考慮的,因為Linux支援多程序程式;而iOS開發中,僅僅就是真對一個(App)程序來開發的;

  1. 方便的通訊和資料交換

    多程序程式結構和多執行緒程式結構有很大的不同;
    對不同程序來說,它們具有獨立的資料空間,要 進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。執行緒則不然, 由於同一程序下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其它執行緒所用, 這不僅快捷,而且方便。

  2. 更高效的利用 CPU

    大多數作業系統是根據時間片輪轉排程,在Linux/Unix中,CPU的排程事件是100ms;而執行緒是CPU最小的排程單元,也就是說當開啟一個新執行緒時,CPU在自己的排程連結串列中去迴圈排程這個執行緒;如果一個執行緒沒有,那麼CPU就會休息;所以說,多執行緒能夠適當提高CPU的利用率!當然CPU呼叫時並非這麼簡單,其中會包含排程的優先順序、中斷等來保證CPU排程是優化的!

    所以,在討論為什麼要使用多執行緒時,如果不說針對多程序而言是那就是沒有沒有參考物件。

  3. 當然,程式中並非開啟越多的執行緒越好,首先執行緒需要消耗記憶體,主執行緒1M、子執行緒是512K;
    其次、執行緒越多,CPU的執行緒連結串列就越長,執行效率會變慢!適當的利用多執行緒!在iOS 中,蘋果公司也為我們封裝一個類`NSOperation`

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

  • 一個iOS程式執行後,預設會開啟1條執行緒,稱為“主執行緒”或“UI執行緒”

  • 主執行緒的主要作用

  • 顯示\重新整理UI介面
  • 處理UI事件(比如點選事件、滾動事件、拖拽事件等)

  • 主執行緒的使用注意

  • 別將比較耗時的操作放到主執行緒中
  • 耗時操作會卡住主執行緒,嚴重影響UI的流暢度,極差的使用者體驗!

在iOS的中多執行緒的實現方法

四種實現方式Threads、NSthread、NSOperation、GCD,使用頻率由低到高!

  1. POSIX Threads

    • 眾多軟體供應商都為自己產品實現多執行緒庫專有版本。這些執行緒庫實現彼此獨立並有很大差別,導致程式設計師難以開發可移植的多執行緒應用程式,因此必須要確立一個規範的程式設計介面標準來充分利用多執行緒所提供的優勢,POSIX -Threads 就是這樣一個規範多執行緒標準介面。

    • POSIX Threads(通常簡稱為Pthreads)定義了建立和操縱執行緒的一套 API 介面,一般 用於 Unix-like POSIX 系統中(如 FreeBSD、GNU/Linux、OpenBSD、Mac OS 等系統)。

    Pthreads 介面可以根據功能劃分四個組:

    • 執行緒管理
    • 互斥量
    • 條件變數
    • 同步

Threads.1. 程管理線

程管理包含了執行緒的建立、終止、等待、分離、設定屬性等操作。
每個執行緒都有從建立到終止的生命週期。
建立執行緒
在程序中建立一個新執行緒的函式是 pthread_create(),原型如下:
```
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

```
說明:執行緒被建立後將立即執行。
如果pthread_create()呼叫成功,函式返回0,否則返回一個非0的錯誤碼
引數說明:
    thread用指向新建立的執行緒的ID;
    attr用來表示一個封裝了執行緒各種屬性的屬性物件,如果attr為NULL,新執行緒就 使用預設的屬性,13.3.4 節將討論執行緒屬性的細節;
    start_routine是執行緒開始執行的時候呼叫的函式的名字,start_routine函式有一個有 指向 void 的指標引數,並有 pthread_create 的第四個引數 arg 指定值;start_routine 函式返回一個指向 void 的指標,這個返回值被 pthread_join 當做退出 狀態處理;
    arg為引數start_routine指定函式的引數。
2. 終止執行緒
    void pthread_exit(void *retval);

執行緒安全

多執行緒程式設計環境中,多個執行緒同時呼叫某些函式可能會產生錯誤結果,這些函式稱為非執行緒安全函式。如果庫函式能夠在多個執行緒中同時執行並且不會互相干擾,那麼這個庫函式 就是執行緒安全(thread-safe)函式;
  • Threads.2. 互斥量
    在作業系統中有許多共享資源不允許使用者並行使用,例如手機的通話,在通話過程中,不能同時去接兩個電話,香這種共享裝置被稱為“排它性資源”,因為它一次只能由一個執行流訪問。執行流必須以互斥的方式執行訪問排它性資源的程式碼。
    臨界區是必須以互斥方式執行的程式碼段,也就是說在臨界區的範圍內只能有一個活動的執行執行緒。

    互斥量(Mutex),又稱為互斥鎖,是一種用來保護臨界區的特殊變數,它可以處於鎖定(locked)狀態,也可以處於解鎖(unlocked)狀態:
    如果互斥鎖是鎖定的,就是一個特定的執行緒持有這個互斥鎖;
    如果沒有執行緒持有這個互斥鎖,那麼這個互斥鎖就處於解鎖狀態。
    每個互斥鎖內部有一個執行緒等待佇列,用來儲存等待該互斥鎖的執行緒。當互斥鎖處於解鎖狀態時,一個執行緒試圖獲取這個互斥鎖時,這個執行緒就可以得到這個互斥鎖而不會阻塞;

    當互斥鎖處於鎖定狀態時,一個執行緒試圖獲取這個互斥鎖時,這個執行緒將阻塞在互斥鎖的等 待執行緒佇列內。

    互斥量是最簡單也是最有效的執行緒同步機制。程式可以用它來保護臨界區,以獲得對排它性資源的訪問權。
    另外,互斥量只能被短時間地持有,使用完臨界資源後應立即釋放鎖。

        pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
        pthread_mutex_lock(&mylock);
        臨界區程式碼
        pthread_mutex_unlock(&mylock);
    

Threads.3. 條件變數
在多執行緒程式設計中僅使用互斥鎖來完成互斥是不夠用的,如以下情形:
假設有兩個執行緒 t1 和 t2,需要這個兩個執行緒迴圈對一個共享變數 sum 進行自增操作, 那麼 t1 和 t2 只需要使用互斥量即可保證操作正確完成,執行緒執行程式碼如所示:

```
pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
void * t1t2(void) {
    thread_mutex_lock(&sumlock);
    sum++;
    pthread_mutex_unlock(&sumlock); 

}
```
如果這時需要增加另一個執行緒 t3,需要 t3 在 count 大於 100 時將 count 值重新置 0 值, 那麼可以 t3 可以實現如下:
```
void * t3 (void) { pthread_mutex_lock(&sumlock); 
    if (sum >= 100) {
      sum = 0;
      pthread_mutex_unlock(&sumlock); 
    } else {
      pthread_mutex_unlock(&sumlock);
      usleep(100); 
    }
}
```
    以上程式碼存在以下問題:
    1) sum 在大多數情況下不會到達 100,那麼對 t3 的程式碼來說,大多數情況下,走的是 else分支,只是 lock 和 unlock,然後 sleep()。這浪費了 CPU 處理時間。
    2) 為了節省 CPU 處理時間,t3 會在探測到 sum 沒到達 100 的時候 usleep()一段時間。 這樣卻又帶來另外一個問題,亦即 t3 響應速度下降。可能在 sum 到達 200 的時候,t3 才會 醒過來。
    這樣時間與效率出現了矛盾,而條件變數就是解決這個問題的好方法。

    在Threads這種場景就需要用等待和通知去解決!
    條件等待函式有 pthread_cond_wait()pthread_cond_timedwait()和兩個條件通知函式有 pthread_cond_signal()和 pthread_cond_broadcast()函式;


    其實這種場景在iOS開發中,就是程序中有相互依賴的關係,A執行完畢,B才能執行;NSOperation中就可以新增依賴,使得A完成之後B才能去執行;

    如果在不同的佇列中,那麼就需要用GCD的dispatch_group_t(佇列)組去解決!
    那麼我覺得dispatch_group_t就是對Threads的通知等待的封裝。