Linux 上多線程編程經驗



本文中我們針對 Linux 上多線程編程的主要特性總結出 5 條經驗,用以改善 Linux 多線程編程的習慣和避免其中的開發陷阱。
熟悉 Linux 平臺的多線程編程
熟悉 Linux 平臺上基本的線程編程的 Pthread 庫 API
多線程開發的最基本概念主要包含三點:
一.線程控制
1. 創建----pthread_create
2. 退出----pthread_exit
3. 等待----pthread_join

二.線程同步之互斥鎖
1. 創建----pthread_mutex_init
2. 銷燬----pthread_mutex_detroy
3. 加鎖----pthread_mutex_lock
4. 解鎖----pthread_mutex_unlock

三.線程同步之條件變量
1. 創建----pthread_cond_init
2. 銷燬----pthread_cond_detroy
3. 單觸發--pthread_cond_signal
4. 廣播----pthread_cond_broadcast
5. 等待----pthread_cond_wait/pthread_cond_timewait

******Linux 線程編程中的 5 條經驗*******

經驗 1. Linux 重複對互斥鎖加鎖實例
在Linux下無法對同一線程中試圖對互斥鎖進行兩次或兩次以上的行爲
代碼如下;
    // 通過默認條件建鎖
    pthread_mutex_t *theMutex = new pthread_mutex_t;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutex_init(theMutex,&attr);
    pthread_mutexattr_destroy(&attr);

    // 遞歸加鎖
    pthread_mutex_lock (theMutex);
    pthread_mutex_lock (theMutex);
    pthread_mutex_unlock (theMutex);
    pthread_mutex_unlock (theMutex);
在以上代碼場景中,問題將出現在第二次加鎖操作。由於在默認情況下,Linux 不允許同一線程遞歸加鎖,因此在第二次加鎖操作時線程將出現死鎖。
解決辦法:顯式地在互斥變量初始化時將設置起 recursive 屬性。
可以解決同一線程遞歸加鎖的問題,又可以避免很多情況下死鎖的發生。
修改後如下:
    pthread_mutexattr_init(&attr);
    // 設置 recursive 屬性
    pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP);
    pthread_mutex_init(theMutex,&attr);
經驗2.注意 Linux 平臺上觸發條件變量的自動復位問題
Linux 平臺的 Pthread (pthread_cond_signal)所採用的模型,當條件變量置位(signaled)以後,不管當前有沒有任何線程在等待,其狀態也會恢復爲復位(unsignaled)狀態。
由於這種差異只發生在觸發沒有被線程等待在條件變量的時刻,因此我們只需要掌握好觸發的時機即可。最簡單的做法是增加一個計數器記錄等待線程的個數,在決定觸發條件變量前檢查下該變量即可。
建議在 Linux 平臺上要出發條件變量之前要檢查是否有等待的線程,只有當有線程在等待時纔對條件變量進行觸發。
經驗3.注意條件返回時互斥鎖的解鎖問題
在 Linux 調用 pthread_cond_wait 進行條件變量等待操作時,我們增加一個互斥變量參數是必要的,這是爲了避免線程間的競爭和飢餓情況。但是當條件等待返回時候,需要注意的是一定不要遺漏對互斥變量進行解鎖。
Linux 平臺上的 pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 函數返回時,互斥鎖 mutex 將處於鎖定狀態。因此之後如果需要對臨界區數據進行重新訪問,則沒有必要對 mutex 就行重新加鎖。但是,隨之而來的問題是,每次條件等待以後需要加入一步手動的解鎖操作。
經驗4.等待的絕對時間問題
超時是多線程編程中一個常見的概念。例如,當你在 Linux 平臺下使用 pthread_cond_timedwait() 時就需要指定超時這個參數,以便這個 API 的調用者最多隻被阻塞指定的時間間隔。但是如果你是第一次使用這個 API 時,首先你需要了解的就是這個 API 當中超時參數的特殊性(就如本節標題所提示的那樣)。我們首先來看一下這個 API 的定義。 pthread_cond_timedwait() 。
pthread_cond_timedwait() 函數定義如下:
    int pthread_cond_timedwait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);
Linux 的絕對時間看似簡單明瞭,卻是開發中一個非常隱晦的陷阱。而且一旦你忘了時間轉換,可以想象,等待你的錯誤將是多麼的令人頭疼:如果忘了把相對時間轉換成絕對時間,相當於你告訴系統你所等待的超時時間是過去式的 1970 年 1 月 1 號某個時間段,於是操作系統毫不猶豫馬上送給你一個 timeout 的返回值,然後你會舉着拳頭抱怨爲什麼另外一個同步線程耗時居然如此之久,並一頭扎進尋找耗時原因的深淵裏。
經驗5.正確處理 Linux 平臺下的線程結束問題
在 Linux 平臺下,當處理線程結束時需要注意的一個問題就是如何讓一個線程善始善終,讓其所佔資源得到正確釋放。在 Linux 平臺默認情況下,雖然各個線程之間是相互獨立的,一個線程的終止不會去通知或影響其他的線程。但是已經終止的線程的資源並不會隨着線程的終止而得到釋放,我們需要調用 pthread_join() 來獲得另一個線程的終止狀態並且釋放該線程所佔的資源。
調用該函數的線程將掛起,等待 th 所表示的線程的結束。 thread_return 是指向線程 th 返回值的指針。需要注意的是 th 所表示的線程必須是 joinable 的,即處於非 detached(遊離)狀態;並且只可以有唯一的一個線程對 th 調用 pthread_join() 。如果 th 處於 detached 狀態,那麼對 th 的 pthread_join() 調用將返回錯誤。
如果你壓根兒不關心一個線程的結束狀態,那麼也可以將一個線程設置爲 detached 狀態,從而來讓操作系統在該線程結束時來回收它所佔的資源。將一個線程設置爲 detached 狀態可以通過兩種方式來實現。一種是調用 pthread_detach() 函數,可以將線程 th 設置爲 detached 狀態。
總之爲了在使用 Pthread 時避免線程的資源在線程結束時不能得到正確釋放,從而避免產生潛在的內存泄漏問題,在對待線程結束時,要確保該線程處於 detached 狀態,否着就需要調用 pthread_join() 函數來對其進行資源回收。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章