天天看點

linux下多線程程式設計入門總結

目錄

    • (一)并行和并發的差別
    • (二)多線程的概念及優缺點
    • (三)建立線程
          • `pthread_create()`建立線程
          • `pthread_join`()阻塞回收
          • `pthread_detach`()線程分離
          • `pthread_exit()`單線程退出
          • `pthread_cancel()`線程取消
    • (四)程序與線程控制語句對比

(一)并行和并發的差別

 并行的反義詞是串行,假如有一個10萬資料量的數組讓我們挑出這裡的最大值,串行的處理方法就是對這個數組掃描一遍,找到其最大值當然這樣的效率非常慢,而并行可以看作将這10萬個資料分為10組,配置設定多個處理器進行處理找到每個數組的最大值在比較,找出最終結果,這是在多核處理下的并行雖然提高了效率但是涉及到分解,運作,合并等排程任務。在課本上的定義:

在作業系統中是指,一組程式按獨立異步的速度執行,無論從微觀還是宏觀,程式都是一起執行的。

 通常來說隻有多核處理器下才滿足無論從微觀還是宏觀程式是一起執行的。即多條程式在多個處理器下被執行,而下面的并發從宏觀上可看作是多個程序被執行(并行)。

 并發的反義詞是順序,就像我們小時候做的數學題:做家務燒水要10分鐘,擦地要15分鐘,洗衣服要20分鐘,做完一共要多久,有的小孩子嘛可能會45分鐘,這就是順序處理的代表型人物,稍微聰明點的會先讓洗衣機洗衣服,然後插上水壺再去拖地,這樣就是20分鐘,也是并發的核心思想。在我們的課本上并發的定義是:

在作業系統中,并發是指一個時間段中有幾個程式都處于已啟動運作到運作完畢之間,且這幾個程式都是在同一個處理機上運作,但任一個時刻點上隻有一個程式在處理機上運作。

  從定義中了解并發就是一個時刻隻有一條指令被執行,多個程序快速切換執行指令,在單核處理器下,宏觀上可看作是并行,并發的實作通常要靠多線程來實作,又因為線程之間是搶占式排程是以我們需要了解同步和互斥的概念。

  互斥:因為在很多情況下,線程使用的是局部變量,其配置設定的空間線上程的棧空間内,此時這個變量隻能被該線程使用。而還有些變量需要線上程空間内共享,也就是多個線程通路的公共資源(臨界資源),通路臨界資源的代碼區叫做臨界區,而互斥就是為了保護每個時刻隻有一個執行流進入臨界區通路臨界資源,對其進行保護。執行順序一般是無序的。實作機制通過上鎖:加鎖 —> 執行臨界區代碼 —> 釋放鎖。

  同步:同步關系中往往包含着互斥,其控制着線程與線程間的執行順序,例如一個線程的運作要依賴另一個線程運作後的結果,是以他們間就需要同步來協調讓後者先運作而前者等待。協調多個線程合作完成任務,執行順序一般是有序的。

(二)多線程的概念及優缺點

 線程是作業系統能夠進行運算排程的最小機關,它被包含在程序中,是程序(最小配置設定資源機關)中實際運作機關,其也被稱為輕量程序(lighr weight process)通常指核心線程。

  • 優點:建立代價小,占用資源少,資料共享、資料通訊友善。
  • 缺點:實作難度高,健壯性降低。(相對優點來說,缺點幾乎可忽略)

(三)建立線程

 在建立一個程序後,會産生一個預設的線程,通常被叫做主線程(或控制線程),在linux下c/c++程式中,主線程就是main函數進入的線程,其調用

pthread_creat()

建立的線程稱為子線程,子線程也可通過使用者指定其入口函數,每個函數通過

pthread_self()

擷取其自身的線程ID。線上程模型中除主線程外,其他線程一旦被建立,其關系是同等的,每個程序可建立的線程數根據具體實作決定。

linux下多線程程式設計入門總結

 主線程和子線程的預設關系是:無論子線程執行完畢否,一旦主線程執行完畢退出,所有子線程執行都會終止。

  此時該程序結束或僵死,部分線程保持終止執行但未銷毀的狀态,程序必須線上程銷毀後銷毀,此時程序進入僵死狀态,線程函數執行後退出,或終止,線程進入終止狀态,但為其配置設定的資源不一定釋放,取決于線程屬性。在此,主線程和子線程通常定義為如下兩種關系:

  1、可會和(joinable):在這種關系下,主線程需要明确執行等待操作,在子線程結束後,主線程完成等待,和子線程會和,繼續執行等待後的操作。在主線程的線程函數内部調用子線程對象的

wait()

函數實作,即使子線程在主線程之前完成,進入終止态,必須完成會和操作,否則系統不會主動銷毀線程及其配置設定的系統資源。

  2、相分離(detached):表示主線程和子線程無需會和,在這種情況下,子線程進入終止狀态。因為讓主線程逐個等待子線程結束或者安排每個子線程結束的等待順序在子線程較多的情況下實作是困難的,是以在并發子線程較多的情況下通常使用相分離的方式。

pthread_create()

建立線程
#include<pthread.h>  //頭檔案

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,
                           (void*)(*start_rtn)(void*),void *arg);  //函數原型
           

參數說明:

  第一個參數:tidp是一個pthread_t類型的指針,用來傳回該線程的線程ID,每個線程都能通過

pthread_self()

擷取自己的線程ID(pthread_t類型)。

  第二個參數:是線程的屬性,是一個pthread_attr_t類型的結構體,定義如下:

typedef struct
 {
      int                           detachstate;     //線程的分離狀态
      int                           schedpolicy;     //線程排程政策
      struct sched_param            schedparam;      //線程的排程參數
      int                           inheritsched;    //線程的繼承性
      int                           scope;           //程的作用域
      size_t                        guardsize;       //線程棧末尾的警戒緩沖區大小
      int                           stackaddr_set;
      void *                        stackaddr;       // 線程棧的位置
      size_t                        stacksize;       //線程棧的大小
 }pthread_attr_t;
           

  對于這些屬性,我們需要設定線程的分離狀态,必要時修改線程棧的大小,線程建立後預設是可會和(joinable)狀态,該狀态需要用

pthread_join

等待退出,否則在子線程結束時就會記憶體洩漏,是以我們在建立線程時一般通過以下方法設定分離狀态:

  1、線上程中調用

pthread_detach(pthread_self)

(較簡單)。

  2、建立線程時設定屬性為PTHREAD_CREAT_DETACHED。

  第三個參數:start_rtn是一個函數指針,指向函數原型為

void *func(void *)

的函數,也就是建立子程序要執行任務的函數。

  第四個參數:arg是傳給所調用的函數的參數,當有多個參數要傳入時,則要封裝到一個結構體,傳入指向這個結構體的指針。

  通過下面的例子了解如何建立線程:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

void *fun(void *arg)
{
    int       *p = (int *)arg;
    printf("I'm child thread, Thread ID = %lu,The shared_var = %d\n", pthread_self(), *p);
    return NULL;
}

int main(void)
{
    int                shared_var = 1000;
    pthread_t          tid;
    pthread_attr_t     thread_attr;

    if( pthread_attr_init(&thread_attr) )  //對線程初始化
    {
           printf("pthread_attr_init() failure :%s\n", strerror(errno));
           return -1;
    }

    if( pthread_attr_setstacksize(&thread_attr, 120*1024))  //初始線程棧的大小
    {
           printf("pthread_attr_setstacksize() failure :%s\n", strerror(errno));
           return -1;
    }

    if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) ) //設定分離狀态
    {
           printf("pthread_attr_getstackaddr() failure :%s\n", strerror(errno));
           return -1;
    }

    pthread_create(&tid, NULL, fun, &shared_var);
    printf("I'm main thread, my pid = %d\n", getpid());

    pthread_attr_destroy(&thread_attr); //銷毀初始化結構
    pthread_join(tid,NULL);  //阻塞等待線程退出

    return 0;
}
           

  運作結果如下:

[email protected]-14:~/exercise$ gcc example.c -o thread -lpthread   //線程編譯時需要帶-lpthread選項
[email protected]-14:~/exercise$ ./thread 
I'm main thread, my pid = 10680
I'm child thread, Thread ID = 140628718462720,The shared_var = 1000
           

pthread_join

()阻塞回收
int pthread_join(pthread_t thread, void **retval);  // 成功傳回0 失敗傳回錯誤号
													// 參數線程ID 和 參數退出狀态(通常為NULL)
           

 其中

pthread_join

函數 阻塞等待線程退出,擷取線程退出狀态 其作用,對應程序中 waitpid() 函數。因為線上程初始化的時候設定了分離模式,是以如果不阻塞等待的話運作後将會是如下結果:

[email protected]-14:~/exercise$ gcc example.c -o thread -lpthread
[email protected]-14:~/exercise$ ./thread 
I'm main thread, my pid = 10744
           

pthread_detach

()線程分離

 上面提到過如果不使用參數初始化的話,也可使用:

 但在一般情況下線程終止後,終止狀态一直保留到其他線程調用:

pthread_join

擷取狀态位置,而調用了

pthread_detach

函數後縣城一旦中止就會立即回收它所占用的資源而不保留終止狀态,不可調用了

pthread_detach

函數的線程調用

pthread_join

将傳回EINVAL錯誤,即如上兩函數不可同時調用。

pthread_exit()

單線程退出

  線上程中禁止使用

exit()

函數,該函數會使程序内的所有線程全部退出,可以使用

pthread_exit

,将單個線程退出。

pthread_cancel()

線程取消

pthread_cancel

可以殺死(取消)線程 其作用,對應程序中 kill() 函數。

#include <stdio.h>
#include <pthread.h>


static void* pthread_func1(void* arg)
{
    while(1)
    {
        printf("hello world\n");
        sleep(1);
    }
    return NULL;
}

static void* pthread_func2(void* arg)
{
    int a = 0;
    for( ;; )
        a++;
    return NULL;
}

int main(int argc, char const *argv[]) {
    pthread_t tid;
    pthread_create(&tid, NULL, pthread_func1, NULL);

    pthread_cancel(tid);
    pthread_join(tid, NULL);
    return 0;
}
           

  上面這個例子中當線程運作

pthread_func1

函數時輸出的是:

[email protected]-14:~/exercise$ ./cancel 
hello world
[email protected]-14:~/exercise$ 
           

  而運作

pthread_func2

函數時輸出結果為:

[email protected]-14:~/exercise$ ./cancel 
                                        //在pthread_func2函數中死循環
           

  因為

pthread_cancel()

函數不是直接退出線程,而是在系統中設定一個

cancelpoint

,在調用系統調用時會檢查是否設定了

cancelpoint

,如果設定就會退出,例如

pthread_func1

中的

sleep(1)

就屬于系統調用,而

pthread_func2

是c語言的for循環庫函數,是以不會退出線程。

(四)程序與線程控制語句對比

線程 程序
fork (建立程序 ) pthread_create (建立線程)
wait(程序阻塞) pthread_join(線程阻塞)
exit(程序退出) pthread_exit(單個線程退出)
getpid(傳回程序ID) pthread_self(傳回線程ID)
kill(殺死程序) pthread_cancel(線程取消)

參考部落格:https://blog.csdn.net/qq_27396861/article/details/95752790

繼續閱讀