iOS多執行緒Pthreads篇

Perfect_Dream發表於2017-12-21

本篇涉及內容

  • Pthreads基礎釋義
  • Pthreads執行緒釋義與使用
  • 鎖(互斥鎖、自旋鎖、讀寫鎖、條件鎖)的基礎釋義
  • Pthreads使用
  • 鎖(互斥鎖、自旋鎖、讀寫鎖、條件鎖)的使用

Pthreads

百度百科:POSIX執行緒(POSIX threads),簡稱Pthreads,是執行緒的POSIX標 準。該標準定義了建立和操縱執行緒的一整套API。在類Unix作業系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作為作業系統的執行緒。Windows作業系統也有其移植版pthreads-win32。

維基百科:POSIX執行緒(英語:POSIX Threads,常被縮寫為Pthreads)是POSIX的執行緒標準,定義了建立和操縱執行緒的一套API。實現POSIX 執行緒標準的庫常被稱作Pthreads,一般用於Unix-like POSIX 系統,如Linux、Solaris。但是Microsoft Windows上的實現也存在,例如直接使用Windows API實現的第三方庫pthreads-w32;而利用Windows的SFU/SUA子系統,則可以使用微軟提供的一部分原生POSIX API。

簡單來說就是作業系統級別使用的執行緒,基於c語言實現,使用難度較大,需要手動管理執行緒生命週期,下邊是一些基礎使用程式碼。

Pthreads常用函式與功能

使用函式前需匯入標頭檔案

#import <pthread.h>
複製程式碼

一. pthread_t

pthread_t用於表示Thread ID,具體內容根據實現的不同而不同,有可能是一個Structure,因此不能將其看作為整數.

二. pthread_equal

pthread_equal函式用於比較兩個pthread_t是否相等.

int pthread_equal(pthread_t tid1, pthread_t tid2)
複製程式碼

三. pthread_self

pthread_self函式用於獲得本執行緒的thread id

pthread _t pthread_self(void);
複製程式碼

四. 建立執行緒

建立執行緒使用pthread_create函式

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, 
複製程式碼
  1. create函式解析
    void *(*start_rtn)(void *), void *restrict arg);
    pthread_t *restrict tidp:返回最後建立出來的Thread的Thread ID
    const pthread_attr_t *restrict attr:指定執行緒的Attributes,後面會講道,現在可以用NULL
    void *(*start_rtn)(void *):指定執行緒函式指標,該函式返回一個void *,引數也為void*
    void *restrict arg:傳入給執行緒函式的引數
    返回錯誤值。
    複製程式碼
  2. pthread函式在出錯的時候不會設定errno,而是直接返回錯誤值
  3. 在Linux 系統下面,在老的核心中,由於Thread也被看作是一種特殊,可共享地址空間和資源的Process,因此在同一個Process中建立的不同 Thread具有不同的Process ID(呼叫getpid獲得)。而在新的2.6核心之中,Linux採用了NPTL(Native POSIX Thread Library)執行緒模型(可以參考這裡這裡),在該執行緒模型下同一程式下不同執行緒呼叫getpid返回同一個PID。
  4. 對建立的新執行緒和當前建立者執行緒的執行順序作出任何假設

五. 終止執行緒

  1. exit, _Exit, _exit用於中止當前程式,而非執行緒

  2. 中止執行緒可以有三種方式:

    a. 線上程函式中return

    b. 被同一程式中的另外的執行緒Cancel掉

    c. 執行緒呼叫pthread_exit函式

  3. pthread_exit和pthread_join函式的用法:

    a. 執行緒A呼叫pthread_join(B,&rval_ptr),被Block,進入Detached狀態(如果已經進入Detached狀態,則pthread_join函式返回EINVAL)。如果對B的結束程式碼不感興趣,rval_ptr可以傳NULL。

    b. 執行緒B呼叫pthread_exit(rval_ptr),退出執行緒B,結束程式碼為rval_ptr。注意rval_ptr指向的記憶體的生命週期,不應該指向B的Stack中的資料。

    c. 執行緒A恢復執行,pthread_join函式呼叫結束,執行緒B的結束程式碼被儲存到rval_ptr引數中去。如果執行緒B被Cancel,那麼rval_ptr的值就是PTHREAD_CANCELLED

    兩個函式原型如下

    void pthread_exit(void *rval_ptr);
    int pthread_join(pthread_t thread, void **rval_ptr);
    複製程式碼
  4. 一個Thread可以要求另外一個Thread被Cancel,通過呼叫pthread_cancel函式:

    void pthread_cancel(pthread_t tid)

    該函式會使指定執行緒如同呼叫了pthread_exit(PTHREAD_CANCELLED)。不過,指定執行緒可以選擇忽略或者進行自己的處理,在後面會講到。此外,該函式不會導致Block,只是傳送Cancel這個請求。

  5. 執行緒可以安排在它退出的時候,某些函式自動被呼叫,類似atexit()函式。 需要呼叫如下函式:

    void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_pop(int execute);

    這兩個函式維護一個函式指標的Stack,可以把函式指標和函式引數值push/pop。執行的順序則是從棧頂到棧底,也就是和push的順序相反。

    在下面情況下pthread_cleanup_push所指定的thread cleanup handlers會被呼叫:

    a.呼叫pthread_exit

    b.相應cancel請求

    c.以非0引數呼叫pthread_cleanup_pop()。(如果以0呼叫pthread_cleanup_pop(),那麼handler不會被呼叫

    有一個比較怪異的要求是,由於這兩個函式可能由巨集的方式來實現,因此這兩個函式的呼叫必須得是在同一個Scope之中,並且配對,因為在pthread_cleanup_push的實現中可能有一個{,而pthread_cleanup_pop可能有一個}。因此,一般情況下,這兩個函式是用於處理意外情況用的,舉例如下:

    void *thread_func(void *arg)
    {
        pthread_cleanup_push(cleanup, “handler”)
       // do something
       Pthread_cleanup_pop(0);
        return((void *)0);
    }
    複製程式碼
  6. 掛起狀態

    預設情況下,一個執行緒A的結束狀態被儲存下來直到pthread_join為該執行緒被呼叫過,也就是說即使執行緒A已經結束,只要沒有執行緒B呼叫 pthread_join(A),A的退出狀態則一直被儲存。而當執行緒處於Detached狀態之時,黨執行緒退出的時候,其資源可以立刻被回收,那麼這個退出狀態也丟失了。在這個狀態下,無法為該執行緒呼叫pthread_join函式。我們可以通過呼叫pthread_detach函式來使指定執行緒進入Detach狀態:

    int pthread_detach(pthread_t tid);
    複製程式碼

    通過修改呼叫pthread_create函式的attr引數,我們可以指定一個執行緒在建立之後立刻就進入Detached狀態

  7. 程式函式和執行緒函式的相關性

Process Primitive Thread Primitive Description
fork pthread_create 建立新的控制流
exit pthread_exit 退出已有的控制流
waitpid pthread_join 等待控制流並獲得結束程式碼
atexit pthread_cleanup_push 註冊在控制流退出時候被呼叫的函式
getpid pthread_self 獲得控制流的id
abort pthread_cancel 請求非正常退出

六. 鎖

  1. 互斥鎖:pthread_mutex_

    a.用於互斥訪問

    b.型別:pthread_mutex_t,必須被初始化為PTHREAD_MUTEX_INITIALIZER(用於靜態分配的mutex,等價於 pthread_mutex_init(…, NULL))或者呼叫pthread_mutex_init。Mutex也應該用pthread_mutex_destroy來銷燬。這兩個函式原型如下:

    int pthread_mutex_init(
        pthread_mutex_t *restrict mutex,
        const pthread_mutexattr_t *restrict attr)
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    複製程式碼

    c.pthread_mutex_lock 用於Lock Mutex,如果Mutex已經被Lock,該函式呼叫會Block直到Mutex被Unlock,然後該函式會Lock Mutex並返回。pthread_mutex_trylock類似,只是當Mutex被Lock的時候不會Block,而是返回一個錯誤值EBUSY。 pthread_mutex_unlock則是unlock一個mutex。這三個函式原型如下:

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    複製程式碼
  2. 自旋鎖:Spin lock

    首先要提的是OSSpinLock已經出現了BUG,導致並不能完全保證是執行緒安全的。

    新版 iOS 中,系統維護了 5 個不同的執行緒優先順序/QoS: background,utility,default,user-initiated,user-interactive。高優先順序執行緒始終會在低優先順序執行緒前執行,一個執行緒不會受到比它更低優先順序執行緒的干擾。這種執行緒排程演算法會產生潛在的優先順序反轉問題,從而破壞了 spin lock。 具體來說,如果一個低優先順序的執行緒獲得鎖並訪問共享資源,這時一個高優先順序的執行緒也嘗試獲得這個鎖,它會處於 spin lock 的忙等狀態從而佔用大量 CPU。此時低優先順序執行緒無法與高優先順序執行緒爭奪 CPU 時間,從而導致任務遲遲完不成、無法釋放 lock。這並不只是理論上的問題,libobjc 已經遇到了很多次這個問題了,於是蘋果的工程師停用了 OSSpinLock。 蘋果工程師 Greg Parker 提到,對於這個問題,一種解決方案是用 truly unbounded backoff 演算法,這能避免 livelock 問題,但如果系統負載高時,它仍有可能將高優先順序的執行緒阻塞數十秒之久;另一種方案是使用 handoff lock 演算法,這也是 libobjc 目前正在使用的。鎖的持有者會把執行緒 ID 儲存到鎖內部,鎖的等待者會臨時貢獻出它的優先順序來避免優先順序反轉的問題。理論上這種模式會在比較複雜的多鎖條件下產生問題,但實踐上目前還一切都好。 OSSpinLock 自旋鎖,效能最高的鎖。原理很簡單,就是一直 do while 忙等。它的缺點是當等待時會消耗大量 CPU 資源,所以它不適用於較長時間的任務。對於記憶體快取的存取來說,它非常合適。 -摘自ibireme

  3. 讀寫鎖:pthread_rwlock_

    a. 多個執行緒可以同時獲得讀鎖(Reader-Writer lock in read mode),但是隻有一個執行緒能夠獲得寫鎖(Reader-writer lock in write mode).

    b. 讀寫鎖有三種狀態

    i.    一個或者多個執行緒獲得讀鎖,其他執行緒無法獲得寫鎖
    ii.   一個執行緒獲得寫鎖,其他執行緒無法獲得讀鎖
    iii.  沒有執行緒獲得此讀寫鎖
    複製程式碼

    c. 型別為pthread_rwlock_t

    d. 建立和關閉方法如下:

    int pthread_rwlock_init(
          pthread_rwlock_t *restrict rwlock,
          const pthread_rwlockattr_t *restrict attr)
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    複製程式碼

    e. 獲得讀寫鎖的方法如下:

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    pthread_rwlock_rdlock:獲得讀鎖
    pthread_rwlock_wrlock:獲得寫鎖
    pthread_rwlock_unlock:釋放鎖,不管是讀鎖還是寫鎖都是呼叫此函式
    複製程式碼

    注意具體實現可能對同時獲得讀鎖的執行緒個數有限制,所以在呼叫 pthread_rwlock_rdlock的時候需要檢查錯誤值,而另外兩個pthread_rwlock_wrlockpthread_rwlock_unlock則一般不用檢查,如果我們程式碼寫的正確的話。

  4. 條件鎖:Conditional Variable

    如果一個執行緒需要等待某一條件才能繼續執行,而這個條件是由別的執行緒產生的,這時候只用鎖就有點捉襟見肘了。要麼不停的輪詢,消耗資源,要麼每隔一段時間查詢一次,喪失了及時性。 條件變數就是為了滿足這種場景而生的,它可以讓一個執行緒等待某一條件,當條件滿足時,會收到通知。 在獲取條件變數並等待條件發生的過程中,也會產生多執行緒的競爭,所以條件變數通常會和互斥鎖一起工作。

    a.條件必須被Mutex保護起來

    b.型別為:pthread_cond_t,必須被初始化為PTHREAD_COND_INITIALIZER(用於靜態分配的條件,等價於pthread_cond_init(…, NULL))或者呼叫pthread_cond_init

    int pthread_cond_init(
          pthread_cond_t *restrict cond,
          const pthread_condxattr_t *restrict attr)
    int pthread_cond_destroy(pthread_cond_t *cond);
    複製程式碼

    c. pthread_cond_wait

    函式用於等待條件發生(=true)。pthread_cond_timedwait類似,只是當等待超時的時候返回一個錯誤值ETIMEDOUT。超時的時間用timespec結構指定。此外,兩個函式都需要傳入一個Mutex用於保護條件

    int pthread_cond_wait(
           pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex);
    int pthread_cond_timedwait(
           pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict timeout);
    複製程式碼

    d. timespec結構定義如下:

    struct timespec {
        time_t tv_sec;       /* seconds */
        long   tv_nsec;      /* nanoseconds */
    };
    複製程式碼

    注意timespec的時間是絕對時間而非相對時間,因此需要先呼叫gettimeofday函式獲得當前時間,再轉換成timespec結構,加上偏移量。

    e. 有兩個函式用於通知執行緒條件被滿足(=true):

    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond);
    複製程式碼

    兩者的區別是前者會喚醒單個執行緒,而後者會喚醒多個執行緒

Pthreads使用方法

一.首先包含標頭檔案 #import <pthread.h> 二.基礎使用測試程式碼

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 建立執行緒——定義一個pthread_t型別變數
    pthread_t thread;
    // 開啟執行緒——執行任務
    /*
    pthread_create(&thread, NULL, run, NULL); 中各項引數含義:
    第一個引數&thread是執行緒物件
    第二個和第四個是執行緒屬性,可賦值NULL
    第三個run表示指向函式的指標(run對應函式裡是需要在新執行緒中執行的任務)
     */
    pthread_create(&thread, NULL, threadStart, NULL);
}
/// > 新執行緒呼叫方法,裡邊為需要執行的任務
void *threadStart (void *data) {
    NSLog(@"啦啦啦啦多執行緒測試啦~:%@",[NSThread currentThread]);
    return NULL;
}
複製程式碼

三.結束當前持有執行緒

在任務結束的時候呼叫
pthread_detach(thread);
pthread_cancel(thread);
或者
pthread_exit ( ) 
複製程式碼
  1. Mutex(互斥鎖)的使用

    a. 鎖的建立

    鎖可以被動態或靜態建立,可以用巨集PTHREAD_MUTEX_INITIALIZER來靜態的初始化鎖,採用這種方式比較容易理解,互斥鎖是pthread_mutex_t的結構體,而這個巨集是一個結構常量,如下可以完成靜態的初始化鎖:

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    複製程式碼

    另外鎖可以用pthread_mutex_init函式動態的建立,函式原型如下:

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
    複製程式碼

    b. 鎖的屬性

    互斥鎖屬性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr);來初始化,然後可以呼叫其他的屬性設定方法來設定其屬性. 互斥鎖的範圍:可以指定是該程式與其他程式的同步還是同一程式內不同的執行緒之間的同步。可以設定為PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。預設是後者,表示程式內使用鎖。可以使用

    int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared)
    pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared)
    複製程式碼

    用來設定與獲取鎖的範圍.

    c. 互斥鎖的型別:

    PTHREAD_MUTEX_TIMED_NP,這是預設值,也就是普通鎖。當一個執行緒加鎖以後,其餘請求鎖的執行緒將形成一個等待佇列,並在解鎖後按優先順序獲得鎖。這種鎖策略保證了資源分配的公平性。 PTHREAD_MUTEX_RECURSIVE_NP,巢狀鎖,允許同一個執行緒對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同執行緒請求,則在加鎖執行緒解鎖時重新競爭。 PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個執行緒請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP型別動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。 PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖型別,僅等待解鎖後重新競爭。 可以用 pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type) pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type) 獲取或設定鎖的型別。

    d. 鎖的釋放

    呼叫pthread_mutex_destory之後,可以釋放鎖佔用的資源,但這有一個前提上鎖當前是沒有被鎖的狀態。

    e. 鎖操作

    對鎖的操作主要包括加鎖 pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖 pthread_mutex_trylock()三個。

    int pthread_mutex_lock(pthread_mutex_t *mutex)
    int pthread_mutex_unlock(pthread_mutex_t *mutex)
    int pthread_mutex_trylock(pthread_mutex_t *mutex)
    複製程式碼

    pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被佔據時返回EBUSY而不是掛起等待

    f. 程式碼

    常用鎖

    __block pthread_mutex_t theLock;
    pthread_mutex_init(&theLock, NULL); 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&theLock);
        NSLog(@"需要執行緒同步的操作1 開始");
        sleep(3);
        NSLog(@"需要執行緒同步的操作1 結束");
        pthread_mutex_unlock(&theLock);
        
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        pthread_mutex_lock(&theLock);
        NSLog(@"需要執行緒同步的操作2");
        pthread_mutex_unlock(&theLock);
        });
    複製程式碼

    遞迴鎖(NSRecursiveLock)

    __block pthread_mutex_t theLock;
    //    pthread_mutex_init(&theLock, NULL);  //普通加鎖建立
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&theLock, &attr);  //遞迴加鎖建立
    pthread_mutexattr_destroy(&attr);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveMethod)(int);
        RecursiveMethod = ^(int value) {
            pthread_mutex_lock(&theLock);
            if (value > 0) {
                NSLog(@"value = %d", value);
                sleep(1);
                RecursiveMethod(value - 1);
            }
            pthread_mutex_unlock(&theLock);
        };
        RecursiveMethod(5);
    });
    複製程式碼
  2. Spin Lock(自旋鎖)的使用

  • 自旋鎖與互斥鎖類似
  • 但不同的是:自旋鎖是非阻塞的,當一個執行緒無法獲取自旋鎖時,會自旋,直到該鎖被釋放,等待的過程中執行緒並不會掛起。(實質上就是,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈在等待該自旋鎖的保持著已經釋放了鎖)。
  • 自旋鎖的使用者一般保持鎖的時間很短,此時其效率遠高於互斥鎖。
  • 自旋鎖保持期間是搶佔失效的
    // 初始化自旋鎖
    static OSSpinLock myLock = OS_SPINLOCK_INIT;
    // 自旋鎖的使用
    -(void)SpinLockTest{
        OSSpinLockLock(&myLock);
        // to do something
        OSSpinLockUnlock(&myLock);
    }
    複製程式碼

OSSpinLock的效率是很高的,可惜在16年1月的時候被發現存在優先順序反轉問題,具體文章可以戳 不再安全的 OSSpinLock

  1. pthread_con_(條件鎖)的使用 在iOS中,條件鎖的實現方式有兩種:

a. POSIX方式

POSIX 提供的相關函式如下:

  • pthread_cond_init 初始化
  • pthread_cond_wait 等待條件
  • pthread_cond_broadcast 傳送廣播,喚醒所有正在等待的執行緒
  • pthread_cond_signal 傳送訊號,喚醒第一個執行緒
  • pthread_cond_destroy 銷燬

POSIX例項

條件變數和互斥鎖一樣,都有靜態動態兩種建立方式,靜態方式使用PTHREAD_COND_INITIALIZER常量,如下: 
pthread_cond_t cond=PTHREAD_COND_INITIALIZER 
動態方式呼叫pthread_cond_init()函式,API定義如下: 
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr) 
複製程式碼

儘管POSIX標準中為條件變數定義了屬性,但在LinuxThreads中沒有實現,因此cond_attr值通常為NULL,且被忽略。

登出一個條件變數需要呼叫pthread_cond_destroy(),只有在沒有執行緒在該條件變數上等待的時候才能登出這個條件變數,否則返回EBUSY。因為Linux實現的條件變數沒有分配什麼資源,所以登出動作只包括檢查是否有等待執行緒。

等待條件有兩種方式:無條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(),其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,其中abstime以與time()系統呼叫相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。 API如下:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
複製程式碼

無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個執行緒同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在呼叫pthread_cond_wait()前必須由本執行緒加鎖(pthread_mutex_lock()),而在更新條件等待佇列以前,mutex保持鎖定狀態,並線上程掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。

激發條件有兩種形式,pthread_cond_signal()啟用一個等待該條件的執行緒,存在多個等待執行緒時按入隊順序啟用其中一個;而pthread_cond_broadcast()則啟用所有等待執行緒。

以下是從網上找到的一個經典的小程式:

初始化程式碼:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

  struct node {
    int n_number;
    struct node *n_next;
} *head = NULL;

  - (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"Pthread_Con_ViewController";
    
    UIButton *startChildButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startChildButton.frame = CGRectMake(0, 300, 200, 40);
    [startChildButton setTitle:@"開啟A測試(POSIX)" forState:UIControlStateNormal];
    [startChildButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [startChildButton addTarget:self action:@selector(aButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startChildButton];
}
複製程式碼

實現程式碼:

  - (void)aButtonClick:(UIButton *)sender {
    pthread_t tid;
    int i;
    struct node *p;
    pthread_create(&tid, NULL, thread_func, NULL);   //子執行緒會一直等待資源,類似生產者和消費者,但是這裡的消費者可以是多個消費者,而不僅僅支援普通的單個消費者,這個模型雖然簡單,但是很強大
    /*[tx6-main]*/
    for (i = 0; i < 10; i++) {
        p = malloc(sizeof(struct node));
        p->n_number = i;
        pthread_mutex_lock(&mtx);             //需要操作head這個臨界資源,先加鎖,
        p->n_next = head;
        head = p;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mtx);           //解鎖
        sleep(1);
    }
    printf("thread 1 wanna end the line.So cancel thread 2./n");
    pthread_cancel(tid);             //關於pthread_cancel,有一點額外的說明,它是從外部終止子執行緒,子執行緒會在最近的取消點,退出執行緒,而在我們的程式碼裡,最近的取消點肯定就是pthread_cond_wait()了。
    pthread_join(tid, NULL);
    printf("All done -- exiting/n");
}

  // [thread_func]
static void cleanup_handler(void *arg)
{
    NSLog(@"Cleanup handler of second thread.");
    free(arg);
    (void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg)
{
    struct node *p = NULL;
    
    pthread_cleanup_push(cleanup_handler, p);
    while (1) {
        //這個mutex主要是用來保證pthread_cond_wait的併發性
        pthread_mutex_lock(&mtx);
        //這個while要特別說明一下,單個pthread_cond_wait功能很完善,為何這裡要有一個while (head == NULL)呢?因為pthread_cond_wait裡的執行緒可能會被意外喚醒,如果這個時候head != NULL,則不是我們想要的情況。這個時候,應該讓執行緒繼續進入pthread_cond_wait
        while (head == NULL)   {
            // pthread_cond_wait會先解除之前的pthread_mutex_lock鎖定的mtx,然後阻塞在等待對列裡休眠,直到再次被喚醒(大多數情況下是等待的條件成立而被喚醒,喚醒後,該程式會先鎖定先pthread_mutex_lock(&mtx);,再讀取資源
            pthread_cond_wait(&cond, &mtx);
            //用這個流程是比較清楚的/*block-->unlock-->wait() return-->lock*/
        }
        p = head;
        head = head->n_next;
        NSLog(@"Got %d from front of queue/n",p->n_number);
        free(p);
        pthread_mutex_unlock(&mtx);             //臨界區資料操作完畢,釋放互斥鎖
    }
    pthread_cleanup_pop(0);
    return 0;
}
複製程式碼

b. NSCondition方式

  • NSCondition:是互斥鎖和條件鎖的結合,即一個執行緒在等待signal而阻塞時,可以被另一個執行緒喚醒,由於作業系統實現的差異,即使沒有傳送signal訊息,執行緒也有可能被喚醒,所以需要增加謂詞變數來保證程式的正確性。關於NSCondition蘋果官方有一篇教程,地址在這Threading Programming Guide
  • NSConditionLock:與NSCondition的實現機制不一樣,當定義的條件成立的時候會獲取鎖,反之,釋放鎖。

NSCondition常用API:

[condition lock];//一般用於多執行緒同時訪問、修改同一個資料來源,保證在同一時間內資料來源只被訪問、修改一次,其他執行緒的命令需要在lock 外等待,只到unlock ,才可訪問
[condition unlock];//與lock 同時使用
[condition wait];//讓當前執行緒處於等待狀態
[condition signal];//CPU發訊號告訴執行緒不用在等待,可以繼續執行
複製程式碼

NSCondition測試程式碼:

    // 建立鎖
    NSCondition *condition = [[NSCondition alloc] init];
    static int count = 0;
    // 生產者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while(count<20)
        {
            [condition lock];
            // 生產
            count ++;
            NSLog(@"生產 = %d",count);
            [condition signal];
            [condition unlock];
        }
    });
    // 消費者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (count>0)
        {
            [condition lock];
            // 消耗
            count --;
            NSLog(@"消耗剩餘 = %d",count);
            [condition unlock];
        }
    });
複製程式碼

NSConditionLock常用API:

  //初始化一個NSConditionLock物件
  - (id)initWithCondition:(NSInteger)condition
  //返回一個Condition
  - (NSInteger)condition
  //在指定時間前嘗試獲取鎖,若成功則返回YES 否則返回NO
  1、– (BOOL)lockBeforeDate:(NSDate *)limit
  //嘗試獲取鎖。在加鎖成功前接收物件的Condition必須與引數Condition 相同
  2、– (void)lockWhenCondition:(NSInteger)condition
  //同上,只是又加上了一個時間
  3、– (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate   *)limit
  //嘗試著獲取鎖
  4、– (BOOL)tryLock 
  //如果接收物件的condition與給定的condition相等,則嘗試獲取鎖
  5、– (BOOL)tryLockWhenCondition:(NSInteger)condition
  //解鎖並設定接收物件的condition
  6、– (void)unlockWithCondition:(NSInteger)condition
複製程式碼

NSConditionLock測試程式碼:

// 建立鎖
    NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:0];
    static int count = 0;
    // 生產者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while(true)
        {
            [condLock lock];
            // 生產
            count ++;
            NSLog(@"生產 = %d",count);
            [condLock unlockWithCondition:(count >= 10 ? 10 : 0)];
            if (count >= 10) {
                break;
            }
            sleep(1);
        }
    });
    
    // 消費者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (true)
        {
            [condLock lockWhenCondition:10];
            // 消耗
            count --;
            NSLog(@"消耗剩餘 = %d",count);
            [condLock unlockWithCondition:(count<=0 ? 0 : 10)];
            sleep(1);
        }
    });
複製程式碼
  1. pthread_rwlock_(讀寫鎖)的使用 a. 作用   讀寫鎖將訪問者分為讀出和寫入兩種,當讀寫鎖在讀加鎖模式下,所有以讀加鎖方式訪問該資源時,都會獲得訪問許可權,而所有試圖以寫加鎖方式對其加鎖的執行緒都將阻塞,直到所有的讀鎖釋放。當在寫加鎖模式下,所有試圖對其加鎖的執行緒都將阻塞。   當讀寫鎖被一個執行緒以讀模式佔用的時候,寫操作的其他執行緒會被阻塞,讀操作的其他執行緒還可以繼續進行。   當讀寫鎖被一個執行緒以寫模式佔用的時候,寫操作的其他執行緒會被阻塞,讀操作的其他執行緒也被阻塞。

b.建立

pthread_rwlock_t rwlock;
複製程式碼

c.初始化

pthread_rwlock_init(&rwlock, NULL);
複製程式碼

d.使用

    UIButton *startChildButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startChildButton.frame = CGRectMake(0, 300, 200, 40);
    [startChildButton setTitle:@"開啟A測試" forState:UIControlStateNormal];
    [startChildButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [startChildButton addTarget:self action:@selector(aButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startChildButton];
    
    UIButton *endChildButton = [UIButton buttonWithType:UIButtonTypeSystem];
    endChildButton.frame = CGRectMake(0, 400, 200, 40);
    [endChildButton setTitle:@"關閉B測試" forState:UIControlStateNormal];
    [endChildButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [endChildButton addTarget:self action:@selector(bButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:endChildButton];
複製程式碼
  - (void)aButtonClick:(UIButton *)sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:0];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:1];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:2];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeBook:3];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:4];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeBook:5];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:6];
    });
}

  - (void)bButtonClick:(UIButton *)sender {
    __block int i;
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            NSString *temp = [NSString stringWithFormat:@"%d", i];
            [self writingLock:temp];
            i--;
        }
    });
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            [self readingLock];
            i--;
        }
    });
}

  - (void)readBookWithTag:(NSInteger )tag {
    pthread_rwlock_rdlock(&rwlock);
    // read
    NSLog(@"讀%tu",tag);
    sleep(3);
    pthread_rwlock_unlock(&rwlock);
}

  - (void)writeBook:(NSInteger)tag {
    pthread_rwlock_wrlock(&rwlock);
    // write
    NSLog(@"寫%tu",tag);
    sleep(3);
    pthread_rwlock_unlock(&rwlock);
}

  -(void)writingLock:(NSString *)temp{
    pthread_rwlock_wrlock(&rwlock);
    // writing
    self.rwStr = temp;
    NSLog(@"寫 == %@", temp);
    sleep(1);
    pthread_rwlock_unlock(&rwlock);
  }

  -(NSString *)readingLock{
    pthread_rwlock_rdlock(&rwlock);
    // reading
    NSString *str = self.rwStr;
    NSLog(@"讀 == %@",self.rwStr);
    pthread_rwlock_unlock(&rwlock);
    return str;
  }
複製程式碼

以上,就是Pthread調研結果



有志者、事竟成,破釜沉舟,百二秦關終屬楚;

苦心人、天不負,臥薪嚐膽,三千越甲可吞吳.

相關文章