1. 程式人生 > >《linux下執行緒的建立,同步和退出》

《linux下執行緒的建立,同步和退出》

概述

        前面有一篇文章專門講述了程序建立,監控和終止,這一篇文章進一步來談談執行緒的建立和同步等操作(這裡指的是POSIX規範下的執行緒,即Pthreads)。和探討程序的文章類似,還是通過講述相關呼叫的使用和注意事項來推進,並提供一些例項來做說明。

第一部分:執行緒的概述,以及和程序的比較

================================================

        首先需要明確一點,程序和執行緒都是核心排程的實體,只是他們各自之間的屬性共享程度不同。這也就決定了他們之間必然存在多種通訊機制,這會在後面的一篇文章中專門介紹。這裡我只是想強調他們都是核心排程的實體,雖然二者有區別,但真的很類似。一個程序可以包含多個子程序,每一個程序又可以包含多個執行緒(不管是父程序還是子程序),程序和執行緒都是為了實現並行而生。意識到他們的共同點能幫助我們更好的理解他們的區別。

        同一程序中的所有執行緒均會獨立執行相同的程式(注意,這裡說的是相同的程式,因為它們共用同一份程式碼段。),且共享同一份全域性記憶體區域(初始化資料段,未初始化資料段和堆)。和程序一樣目的在於實現並行,但相較於程序,執行緒有兩個突出的優點

        1. 執行緒間資料共享很方便。因為同一程式的所有執行緒共享同一份全域性記憶體區域,所以只需要將需要共享的資料以全域性變數或者動態分配的變數的方式儲存即可實現線上程之間的共享(對於這一點的負面影響——競爭問題,會在後面提到)。

        2. 建立執行緒要比建立程序快,因為執行緒相對於程序有更多屬性是共享的,在建立執行緒的時候不需要重新設定這些屬性。這裡列舉幾個常用的屬性:記憶體塊上的程式程式碼段、全域性記憶體和堆;程序ID、父程序ID、程序組ID、會話ID、控制終端;開啟的檔案描述符、訊號處置和資源限制等。

第二部分:執行緒的基本呼叫(常用Pthread API)

================================================

        在使用執行緒之前,有三點要說明一下:

1. 執行緒中使用的errno實際上是一個巨集而不是一個全域性變數。原因很簡單,執行緒之間的全域性變數是共享的,所以為了保證執行緒的errno的真實性需要實現獨立。而執行緒中使用的errno巨集定義即維持了原來的使用習慣也確保了執行緒間的獨立性。

2. 所有Pthread API的返回值,0表示成功,失敗返回一個正值,這個值等於errno巨集的返回值。

3. 原始檔包含標頭檔案 pthread.h ,編譯時加上 -pthread選項。

        下面是常用的 Pthread API 及其簡單說明,重要的是後面備註裡的內容,這是使用這些介面時該注意的地方:

建立執行緒

       int pthread_create(pthread_t *restrict thread,
              const pthread_attr_t *restrict attr,
              void *(*start_routine)(void*), void *restrict arg);

終止執行緒

       void pthread_exit(void *value_ptr);

       int pthread_cancel(pthread_t thread);

       備註:exit 和 return 當然也可以導致執行緒退出。此外,pthread_exit 的引數 value_ptr 不應分配線上程棧中。(因為執行緒退出後執行緒棧會被銷燬,導致這一返回值不能被正常使用。)

獲取/比較執行緒ID

       pthread_t pthread_self(void);

       int pthread_equal(pthread_t t1, pthread_t t2);

       備註:為什麼會有 pthread_equal 這個函式呢?這是因為Pthread執行緒的資料型別的實現是不透明的,SUSv3並未規定如何實現Pthread執行緒的資料型別實現。我們不能直接用"=="來比較兩個執行緒好,標準的可移植用法是使用 pthread_equal 介面。

執行緒清理

       int pthread_detach(pthread_t thread);

       int pthread_join(pthread_t thread, void **value_ptr);

       備註:和程序一樣,執行緒結束後如果不清理,也會變成殭屍。上面的兩個介面可以實現清理執行緒,區別在於 pthread_detach 是使執行緒處於分離狀態,這樣線上程結束時就可以自動清理;而 pthread_join 則類似於 waitpid 呼叫,可用於等待指定執行緒結束並清理之。關於他們的使用注意以下幾點:

       1. 分離的執行緒不能再被 pthread_join 連線,也無法恢復到可連線狀態。

       2. 分離只是設定指定執行緒不能被 pthread_join 連線以及可以自動清理的特性,主執行緒的 return 和其他執行緒的 exit 動作仍然可以促使已分離執行緒退出。

       3. 不同於 waitpid 只能被父程序用來監控子程序狀態,執行緒之間可以相互使用 pthread_join 獲取彼此狀態。

       4. pthread_join 呼叫只能以阻塞的方式工作。

       下面舉一個例項,來說明執行緒的使用的基本方式。例項中,執行緒1負責列印標題欄,執行緒2負責運算(0+1+2+3+4+……+n),執行緒1啟動後將自身設定為分離狀態,而執行緒2運算結束後呼叫函式 pthread_exit 退出。主執行緒等待執行緒2結束後獲取其計算結果,並列印相關資料。

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

static int glob = 0;

static void * fun1(void * arg)
{
    pthread_detach(pthread_self());
    printf("%8s%8s%8s\n","start","end","sum");
    return 0;
}

static void * fun2(void * arg)
{
    int loops =*(int *)arg;
    int j;

    for(j = 1; j <= loops; j++)
        glob = glob + j;

    pthread_exit(&glob);
}

int main(int argc, char* argv[])
{
    pthread_t t1,t2;
    int loops,*sum_p;

    if (argc < 2) {
        printf("Please excute with  a int argument.\n");
        exit(1);
    }

    loops = atoi(argv[1]);

    if (pthread_create(&t1,NULL,fun1,NULL) != 0) {
        printf("Creat thread 1 failed.\n");
        exit(1);
    }

    if (pthread_create(&t2,NULL,fun2,&loops) != 0) {
        printf("Creat thread 2 failed.\n");
        exit(1);
    }

    if (pthread_join(t2,(void**)&sum_p) != 0) {
        printf("Join thread 2 failed.\n");
        exit(1);
    }

    printf("%8d%8d%8d\n",0,loops,*sum_p);
    exit(0);
}
        執行結果如下:
[[email protected] thread]# gcc -pthread -o thread_normal thread_normal.c
[[email protected] thread]# ./thread_normal 10
   start     end     sum
       0      10      55

第三部分:執行緒之間的同步問題

================================================

        正是由於執行緒之間共享了全域性記憶體區,那麼必然出現多執行緒之間對共享變數的競爭。Pthread中的互斥量和條件變數正是用於解決這個問題的良藥,下面針對互斥量和條件變數做以下幾點說明:

1. 互斥量屬於pthread_mutex_t 型別的變數,在使用之前必須對其初始化。初始化的方法有兩種。靜態初始化:互斥量為全域性變數,定義時直接初始化為 PTHREAD_MUTEX_INITIALIZER;動態初始化:互斥量動態分配在堆或棧中,或者是不使用預設屬性的全域性變數。

靜態初始化:pthread_mutex_t  mtx  =  PTHREAD_MUTEX_INITIALIZER

動態初始化:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

2. 互斥量使用注意事項:執行緒不應對已經被自己加鎖的互斥量再次加鎖;執行緒不應對其他執行緒加鎖的互斥量進行解鎖;執行緒不應對尚未加鎖的互斥量做解鎖操作。

3. 互斥量使用的良好習慣:嚴格遵循先加鎖後解鎖的操作;解鎖前要檢查是否已經鎖定;存在多個互斥量時最好設定互斥量之間的層級(也就是每個執行緒都按照同一個順序鎖定互斥量),以防出現死鎖。

4. 對於動態初始化的互斥量使用完畢後,需要使用函式 pthread_mutex_destroy() 將其銷燬,而經該函式銷燬的互斥量可以再次使用函式 pthread_mutex_init() 初始化。

5. 互斥量的加鎖解鎖操作相關呼叫:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

說明:pthread_mutex_lock 是最普通的上鎖操作,相形之下 pthread_mutex_trylock 遇到已鎖定的情況不會阻塞,而直接返回EBUSY的錯誤。pthread_mutex_timedlock 可以設定阻塞的時間,超出時間則返回ETIMEDOUT 錯誤。

6. 條件變數的初始化方法和互斥量類似也有兩種:

靜態初始化:pthread_cond_t  cond  =  PTHREAD_COND_INITIALIZER;

動態初始化:int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

7. 條件變數必須結合互斥量一起使用,條件變數就某個共享變數的狀態變化發出通知,而互斥量則確保對共享變數的訪問的互斥性。

8. 對於動態初始化的條件變數,使用完畢後需要呼叫函式 pthread_cond_destroy() 進行銷燬。已經銷燬的條件變數,只要其所在的堆疊沒有被釋放,仍然可以再次呼叫函式 pthread_cond_init() 進行初始化。

9. 使用條件變數的相關呼叫:

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

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 abstime);

說明:pthread_cond_signal 只會喚醒至少一條執行緒,適用於所有執行緒執行完全相同的任務的情況下;pthread_cond_broadcast 會喚醒所有等待執行緒,適用於每個執行緒執行任務關聯到共享變數的判斷條件不同而執行不同任務的情況。pthread_cond_wait 和 pthread_cond_timedwait 就沒什麼好說的了。

        上述內容足以應對簡單的執行緒使用問題,如果對執行緒安全函式(函式的可重入性)以及執行緒特有資料的內容感興趣,可以看看《Linux/UNIX系統程式設計手冊》。

第四部分:執行緒的取消

================================================

        談到執行緒取消,其實並不那麼簡單。雖然我們知道 exit 和 return 都會導致執行緒退出,但是要控制某個執行緒取消仍有不少細節需要注意:

1. 函式 pthread_cancel() 允許某執行緒向另一個執行緒傳送取消請求,要求目標執行緒取消。

2. 目標執行緒的響應,取決於該執行緒的取消性狀態和型別,設定函式如下:

int pthread_setcancelstate(int state, int *oldstate);

int pthread_setcanceltype(int type, int *oldtype);

說明:新建執行緒的預設取消性狀態和型別分別為 PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DEFERED,分別表示執行緒可以取消,型別為延遲型(取消請求被掛起,直至遇到下一個取消點,所謂的取消點實際上是一些特定的系統呼叫線上程中被呼叫的位置)。取消性狀態還可以設定為 PTHREAD_CANCEL_DISABLE 表示執行緒不可取消,執行緒接收到的取消請求會被掛起直到執行緒取消性狀態被設定為 PTHREAD_CANCEL_ENABLE 。取消性型別還可以被設定為 PTHREAD_CANCEL_ASYNCHRONOUS ,非同步取消表示執行緒接收到取消請求後可能會在任何時點取消執行緒。

3. 可以給執行緒設定一個清理函式棧,其中的清理函式是由開發人員定義的函式,線上程取消時自動呼叫該棧內函式進行執行緒清理操作,例如回覆共享變數狀態和解鎖互斥量等操作。相關呼叫如下:

void pthread_cleanup_push(void (*routine)(void*), void *arg);

void pthread_cleanup_pop(int execute);

        等待上傳例項。

相關推薦

linux執行建立同步退出

概述         前面有一篇文章專門講述了程序建立,監控和終止,這一篇文章進一步來談談執行緒的建立和同步等操作(這裡指的是POSIX規範下的執行緒,即Pthreads)。和探討程序的文章類似,還是通過講述相關呼叫的使用和注意事項來推進,並提供一些例項來做說明。 第一部

Linux執行的掛起恢復

POSIX的Linux作業系統沒有提供執行緒掛起和恢復的例程,在網上找了找,看到一個老外寫的程式,感覺想法不錯,放在這裡大家分享一下。理論上應該可以實現,不過我沒有試,給大家提供一個參考。 (在讀取快取裡的資料時,當快取中沒有資料最好把執行緒掛起) void CPrcThread ::suspend()  {

Linux執行的概念使用

一. 概念   首先Linux並不存在真正的執行緒,Linux的執行緒是使用程序模擬的。當我們需要在一個程序中同時執行多個執行流時,我們並不可以開闢多個程序執行我們的操作(32位機器裡每個程序認為它 獨享 4G的記憶體資源),此時便引入了執行緒,例如當我們既需

Linux執行同步的幾種方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)   鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖   int pthread_mutex_init(pthread_mutex_t *m

Qt QThread 執行建立執行同步,執行通訊 例項

1.  繼承QThread, 實現run()方法, 即可建立執行緒。 2. 例項1 程式碼 myThread.h #ifndef MYTHREAD_H #define MYTHREAD_H #include <QThread> class myThrea

Linux執行同步的幾種常見方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)  鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖  int pthread_mutex_init(pthread_mutex_t *mutex

Linux執行間通訊及同步

1、Linux “執行緒” 程序與執行緒之間是有區別的,不過Linux核心只提供了輕量程序的支援,未實現執行緒模型。Linux是一種“多程序單執行緒”的作業系統。Linux本身只有程序的概念,而其所謂的“執行緒”本質上在核心裡仍然是程序。 大家知道,程序是資源分配的單位,同

Java高併發——多執行協作同步控制

      繼上一篇:Java高併發——多執行緒基礎 中講到,共享資源的合理使用,才能夠使多執行緒程式有條不紊的執行。其中我們通過synchronized來實現臨界區資源的是否可以訪問。而,這篇我們來重點總結synchronized的增強替代版鎖,以及其它JD

(四)多執行說學逗唱:執行險惡變數執行安全不得不防

(一)多執行緒說學逗唱:關於執行緒那不得不說的二三事 (二)多執行緒說學逗唱:新手村偶遇Thread類 (三)多執行緒說學逗唱:村口的老R頭是個掃地僧(Runnable) 出了新手村,以後的路可就不那麼好走了,到底現在也是個江湖人,都必須經歷點困難挫折,要不以後拿什

linux執行程式設計用 pthread_cond_timedwait 代替sleep

摘要:多執行緒程式設計中,執行緒A迴圈計算,然後sleep一會接著計算(目的是減少CPU利用率);存在的問題是,如果要關閉程式,通常選擇join執行緒A等待執行緒A退出,可是我們必須等到sleep函式返回,該執行緒A才能正常退出,這無疑減慢了程式退出的速度。當然,你可以terminate執行緒A,但

執行建立同級目錄上下級目錄

人類進步的唯一動力就是懶,而程式碼就是幫助人們去做一些人們懶得做的事情,比如一些重複性的工作。看過我之前的部落格的大佬們都知道,我已經懶到一種境界了。 今天在整理檔案的時候,我發現我需要幾十個資料夾,在裡面,還有子資料夾,當然,我是懶得一個一個去建立的。既然自己懶得做,就寫

linux執行程式設計你還在用sleep麼?用pthread_cond_timedwait吧

摘要:多執行緒程式設計中,執行緒A迴圈計算,然後sleep一會接著計算(目的是減少CPU利用率);存在的問題是,如果要關閉程式,通常選擇join執行緒A等待執行緒A退出,可是我們必須等到sleep函式返回,該執行緒A才能正常退出,這無疑減慢了程式退出的速度。當然,你可以terminate執行緒A,但這樣做

java執行安全同步非同步

執行緒是比程序更小的執行單位,是在程序基礎上進行的進一步劃分。所謂多執行緒是指程序在執行過程中可以產生多個同時存在、同時執行的執行緒。多程序機制可以合理利用資源,提高程式的執行效率。一個程序至少包含一

iOS多執行佇列執行的排列組合結果分析

本文是對以往學習的多執行緒中知識點的一個整理。 多執行緒中的佇列有:序列佇列,併發佇列,全域性佇列,主佇列。 執行的方法有:同步執行和非同步執行。那麼兩兩一組合會有哪些注意事項呢? 如果不是在董鉑然部落格園看到這邊文章請 點選檢視原文 提到多執行緒,也就是四種,pthread,NSthread,GCD

C++ Boost 多執行(九)生產者消費者問題

#include <iostream> #include <boost/thread.hpp> using namespace std; class Account { pu

執行如何實現同步通訊

執行緒同步 什麼是執行緒同步? 當使用多個執行緒來訪問同一個資料時,非常容易出現執行緒安全問題(比如多個執行緒都在操作同一資料導致資料不一致),所以我們用同步機制來解決這些問題。 實現同步機制有兩個方法:1。同步程式碼塊:synchronized(同一個資料){} 同一個資

同一程序執行共享的資料獨有的資料

  在 windows 等平臺上,不同執行緒預設使用同一個堆,所以用 C 的 malloc (或者 windows 的 GlobalAlloc)分配記憶體的時候是使用了同步保護的。如果沒有同步保護,在兩個執行緒同時執行記憶體操作的時候會產生競爭條件,可能導致堆內記憶體管理混亂。比如兩個執行緒分配了統一塊記憶體

Linux執行JAVA程式——JRE安裝配置

JAVA程式的執行必須要安裝JAVA RUNTIME ,也就是執行所需要的環境;我們可以通過安裝JRE 或者JDK 所獲得;如果我們只是應用不是開發,只下載JRE 的包就足夠; JDK包裡面也包含JRE;本文以JRE的安裝為例;JRE中還包括瀏覽器所需要的JAVA外掛;一、下載JRE;http://f

解決MFC執行建立的一個編譯錯誤

錯誤的資訊為:error C2665: 'AfxBeginThread' : none of the 2 overloads can convert parameter 1 from type 'unsigned int (void *)' 今天在公司用winsdk寫了個執

Linux c 執行屬性執行優先順序的修改

執行緒屬性的設定,網上找的文章總感覺不夠全面,還是結合man手冊檢視。 執行緒屬性設定,分兩個方式,一種是在建立之前,通過pthread_attr_t 結構體傳入,另一種,是執行緒建立完已經在執行時,通過部分函式設定。一般常見的是建立執行緒時傳NULL,使用預設屬性,後續執