iOS多線程Pthreads篇

本篇涉及內容html

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

Pthreads

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

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

簡單來講就是操做系統級別使用的線程,基於c語言實現,使用難度較大,須要手動管理線程生命週期,下邊是一些基礎使用代碼。ios

Pthreads經常使用函數與功能

使用函數前需導入頭文件算法

#import <pthread.h>
複製代碼

一. pthread_t小程序

pthread_t用於表示Thread ID,具體內容根據實現的不一樣而不一樣,有多是一個Structure,所以不能將其看做爲整數.緩存

二. pthread_equal安全

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

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
  一、– (BOOL)lockBeforeDate:(NSDate *)limit
  //嘗試獲取鎖。在加鎖成功前接收對象的Condition必須與參數Condition 相同
  二、– (void)lockWhenCondition:(NSInteger)condition
  //同上,只是又加上了一個時間
  三、– (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate   *)limit
  //嘗試着獲取鎖
  四、– (BOOL)tryLock 
  //若是接收對象的condition與給定的condition相等,則嘗試獲取鎖
  五、– (BOOL)tryLockWhenCondition:(NSInteger)condition
  //解鎖並設置接收對象的condition
  六、– (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調研結果



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

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

相關文章
相關標籤/搜索