iOS 多線程系列 -- pthread

iOS 多線程系列 -- 基礎概述
iOS 多線程系列 -- pthread
iOS 多線程系列 -- NSThread
iOS 多線程系列 -- GCD全解一(基礎)
iOS 多線程系列 -- GCD全解二(常用方法)
iOS 多線程系列 -- GCD全解三(進階)
iOS 多線程系列 -- NSOperation
測試Demo的GitHub地址

1. pthread概述

  • pthread 是 POSIX 多線程開發框架,是跨平臺的 C 語言框架,需要自己管理線程的創建銷毀等操作。
  • pthread_t ,用于標識一個線程,不能單純看成整數,通過頭文件可以看到是_opaque_pthread_t 類型的結構體指針
  • pthread_attr_t,線程的屬性,通過頭文件可以看到是一個_opaque_pthread_attr_t類型結構體

2. pthread中常用API

2.1 pthread_create

int pthread_create(pthread_t _Nullable * _Nonnull __restrict, const pthread_attr_t * _Nullable __restrict,void * _Nullable (* _Nonnull)(void * _Nullable),void * _Nullable __restrict);
  • 創建一個線程,創建成功返回0,失敗返回對應錯誤碼
  • 參數解析:
    • 第一個參數為指向線程標識符的指針
    • 第二個參數用來設置線程屬性,中介紹過線程的結構,我們可以初始化一個pthread_attr_t變量指定優先級等屬性,一般可以傳入Null,采用缺省值
    • 第三個參數是線程運行函數的起始地址
    • 最后一個參數是傳給運行函數的參數,如使用示例中的run2方法

2.2 pthread_join

int pthread_join(pthread_t , void * _Nullable * _Nullable)
  • 等待某個線程執行完畢,這個函數是一個線程阻塞的函數,將一直阻塞到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回
  • 參數解析:
    • 第一個參數為被等待的線程標識符
    • 第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值

2.3 pthread_detach

int pthread_detach(pthread_t);
  • 將該子線程的狀態設置為detached,則該線程運行結束后會自動釋放所有資源
  • pthread_detach 和 pthread_join 回收線程資源的區別:
    • pthread_join 會同步等待子線程任務結束后回收其資源,如果該子線程沒有運行結束,父線程會被阻塞,在有些情況下我們并不希望如此,就可以用pthread_detach
    • pthread_detach 不會阻塞調用線程
  • 使用:
    • 父線程調用: pthread_detach(thread2);
    • 也可以在子線程中調用: pthread_detach(pthread_self());
      pthread_self()獲取當前線程ID

2.4 pthread_kill

int pthread_kill(pthread_t, int);
  • 該函數可以用于向指定的線程發送信號:

  • 如果線程內不對信號進行處理,則調用默認的處理程式,如SIGQUIT信號會退出終止線程,SIGKILL會殺死線程等等,可以調用signal(SIGQUIT, sig_process_routine); 來自定義信號的處理程序。

  • 參數解析:

    • 第一個參數表示線程的標識符
    • 第二個參數表示傳遞的signal參數,一般都是大于0的,這時系統默認或者自定義的都是有相應的處理程序。常用信號量宏可以在這里查看,#import <signal.h> .signal為0時,是一個被保留的信號,一般用這個保留的信號測試線程是否存在。
  • pthread_kill 返回值如下:

    • 0:調用成功。
    • ESRCH:線程不存在。
    • EINVAL:信號不合法
    • 測試線程是否存在/終止的方法
  - (void)testThreadLife:(pthread_t)thread
{
    int kill_ret = pthread_kill(thread,0);// 系統定義的信號量宏,如:SIGUSR2,
    if(kill_ret == ESRCH)
        NSLog(@"指定的線程不存在或者是已經終止\n");
    else if(kill_ret == EINVAL)
        NSLog(@"調用傳遞一個無用的信號\n");
    else
        NSLog(@"線程存在\n");

}

2.5 線程取消相關的pthread函數

2.5.1 pthread_cancel
int pthread_cancel(pthread_t) __DARWIN_ALIAS(pthread_cancel);
  • 發送終止信號給thread線程,如果發送成功則返回0,否則為非0值。注意:發送成功并不意味著thread會終止,原因看下面pthread_setcancelstate!
2.5.2 pthread_setcancelstate
int pthread_setcancelstate(int , int * _Nullable)
  • 設置本線程對Cancel信號的反應state,有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE;old_state如果不為NULL則存入原來的Cancel狀態以便恢復。
  • 兩種反應state解析:
    • PTHREAD_CANCEL_ENABLE:表示可以接收處理取消信號,設置線程狀態為CANCEl,并終止任務執行
    • PTHREAD_CANCEL_DISABLE : 忽略cancel信號,繼續執行任務
2.5.3 pthread_setcanceltype
int pthread_setcanceltype (int type, int *oldtype) 
  • 設置本線程取消動作的執行時機,type有兩種取值僅當Cancel狀態為Enable時有效:
    • PTHREAD_CANCEL_DEFFERED ,表示收到信號后繼續運行至下一個取消點再退出,推薦做法,因為在終止線程之前必須要處理好內存回收防止內存泄漏,而手動設置取消點這種方式就可以讓我們很自由的處理內存回收時機
    • PTHREAD_CANCEL_ASYCHRONOUS,立即執行取消動作(退出),不推薦這樣操作,可能造成內存泄漏等問題
  • oldtype如果不為NULL則存入運來的取消動作類型值
void * cancelRun (void *prama)
{
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 忽略取消信號,即使收到其他線程調用pthread_cancel,也會繼續執行任務
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);// 設置收到取消信號,立即取消
    if (!prama) {
        NSLog(@"run1 prama = null");
    } else {
        NSLog(@"run1 prama = %d\n", (int)(*((int*)prama)));
    }
    for (int i = 0; i<5000; i++) {
        NSLog(@"---run1--%d---%@",i,[NSThread currentThread]);
    }
    return &a;

}
2.5.4 pthread_testcancel 設置取消點
  • 線程取消的方法是向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態決定.
  • 線程接收到CANCEL信號的缺省處理(即pthread_create()創建線程的缺省狀態)是繼續運行至取消點,也就是說設置一個CANCELED狀態,線程繼續運行,只有運行至Cancelation-point的時候才會退出
  • 常用設置取消點的方法有:pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()
  • 詳情可以去看demo中下圖所示測試:


3 API總結

3.1 操作函數

pthread_create():創建一個線程
pthread_exit():終止當前線程
pthread_cancel():中斷另外一個線程的運行
pthread_join():阻塞當前的線程,直到另外一個線程運行結束
pthread_attr_init():初始化線程的屬性
pthread_attr_setdetachstate():設置脫離狀態的屬性(決定這個線程在終止時是否可以被結合)
pthread_attr_getdetachstate():獲取脫離狀態的屬性
pthread_attr_destroy():刪除線程的屬性
pthread_kill():向線程發送一個信號
pthread_equal(): 對兩個線程的線程標識號進行比較
pthread_detach(): 分離線程
pthread_self(): 查詢線程自身線程標識號

3.2 同步函數

數據類型:
  • pthread_mutex_t 互斥量,用于互斥訪問,用pthread_mutex_init方法進行初始化
  • pthread_cond_t 條件變量,調用pthread_cond_init初始化
  • 互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線程間的同步
方法
  • pthread_mutex_lock():占有互斥鎖(阻塞操作),函數調用會阻塞直到互斥量被unlock,
  • pthread_mutex_init() 初始化互斥鎖
  • pthread_mutex_destroy() 刪除互斥鎖
  • pthread_mutex_trylock():試圖占有互斥鎖(不阻塞操作)。即當互斥鎖空閑時,將占有該鎖;否則,立即返回一個錯誤值EBUSY。常用于死鎖檢測,根據返回的錯誤信息程序員對死鎖做出相應的處理
  • pthread_mutex_unlock(): 釋放互斥鎖
其他方法

pthread_cond_init():初始化條件變量
pthread_cond_destroy():銷毀條件變量
pthread_cond_signal(): 喚醒第一個調用pthread_cond_wait()而進入睡眠的線程
pthread_cond_wait(): 等待條件變量的特殊條件發生
pthread_key_create(): 分配用于標識進程中線程特定數據的鍵
pthread_setspecific(): 為指定線程特定數據鍵設置線程特定綁定
pthread_getspecific(): 獲取調用線程的鍵綁定,并將該綁定存儲在 value 指向的位置中
pthread_key_delete(): 銷毀現有線程特定數據鍵
pthread_attr_getschedparam();獲取線程優先級
pthread_attr_setschedparam();設置線程優先級

4. 使用示例:

void即“無類型”,void *則為“無類型指針”,可以指向任何數據類型

- (void)testCreatJoinDetach
{
    pthread_t thread1;
    pthread_attr_t att;
    pthread_attr_init(&att);
    int result = pthread_create(&thread1, &att, run1, nil);//創建一個線程
    if (result == 0) {
        NSLog(@"創建線程 OK");
    } else {
        NSLog(@"創建線程失敗 %d", result);
    }
    NSLog(@"111(*thread1).__sig = %ld ,(*thread1).__opaque = %s, thread1 = %p",(*thread1).__sig ,(*thread1).__opaque , thread1);
    
    NSLog(@"before pthread_join ");
    void * thread1Return;
    pthread_join(thread1, &thread1Return);//當前線程被阻塞,等待線程1結束后恢復
    NSLog(@"after pthread_join ; thread1Return = %zd ",(!thread1Return) ? 0 : (int)(*((int *)thread1Return)));

    pthread_t thread2;
    int a = 2;
    pthread_create(&thread2, NULL, (void *)run2, &a);
    pthread_detach(thread2); // 或者在run2中調用pthread_detach(pthread_self());
}

int a = 88;
void * run1 (void *prama)
{
    if (!prama) {
        NSLog(@"run1 prama = null");
    } else {
        NSLog(@"run1 prama = %d\n", (int)(*((int*)prama)));
    }
    for (int i = 0; i<3; i++) {
        NSLog(@"---run1--%d---%@",i,[NSThread currentThread]);
    }
    return &a;
}

void run2 (void *prama)
{
    if (!prama) {
        NSLog(@"run2 prama = null");
    } else {
        NSLog(@"run2 prama = %d\n", (int)(*((int*)prama)));
    }
    for (int i = 0; i<3; i++) {
        NSLog(@"--run2---%d---%@",i,[NSThread currentThread]);
    }
//    pthread_detach(pthread_self());
}

打印結果如下:

**2017-06-27 11:28:42.393 Test - ****多線程****[38965:5126933] pthread_join ****前**
**2017-06-27 11:28:42.394 Test - ****多線程****[38965:5127082] ---run--0---<NSThread: 0x600000271980>{number = 3, name = (null)}**
**2017-06-27 11:28:42.394 Test - ****多線程****[38965:5127082] ---run--1---<NSThread: 0x600000271980>{number = 3, name = (null)}**
**2017-06-27 11:28:42.394 Test - ****多線程****[38965:5127082] ---run--2---<NSThread: 0x600000271980>{number = 3, name = (null)}**
**2017-06-27 11:28:42.395 Test - ****多線程****[38965:5126933] pthread_join ****后**** thread1Return = 0**
**prama from thread2 2**
**2017-06-27 11:28:42.395 Test - ****多線程****[38965:5127083] --run2---0---<NSThread: 0x60800026f7c0>{number = 4, name = (null)}**
**2017-06-27 11:28:42.395 Test - ****多線程****[38965:5127083] --run2---1---<NSThread: 0x60800026f7c0>{number = 4, name = (null)}**

**2017-06-27 11:28:42.396 Test - ****多線程****[38965:5127083] --run2---2---<NSThread: 0x60800026f7c0>{number = 4, name = (null)}**

5. 參考資料

iOS開發 - 多線程實現方案之Pthread篇

linux多線程全面解析

pthread_cancel引起的死鎖

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 線程基礎 線程是進程的一個執行單元,執行一段程序片段,線程共享全局變量;線程的查看可以使用命令或者文件來進行查看;...
    秋風弄影閱讀 761評論 0 0
  • 創建線程 C99新增restrict用于限定指針;該關鍵字用于告訴編譯器,所有修改該指針所指向的內容的操作全部都是...
    Joe_HUST閱讀 939評論 0 0
  • 轉自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay閱讀 1,631評論 0 52
  • 線程 在linux內核那一部分我們知道,線程其實就是一種特殊的進程,只是他們共享進程的文件和內存等資源,無論如何對...
    大雄good閱讀 682評論 0 2
  • linux線程同步 信號燈:與互斥鎖和條件變量的主要不同在于"燈"的概念,燈亮則意味著資源可用,燈滅則意味著不可用...
    鮑陳飛閱讀 711評論 0 2