Linux多線程開發

1.進程與線程的區別

2.線程的創建

函數原型:

int  pthread_create(pthread_t *thread, const pthread_attr_t *attr,void* (*start_routine)(void *), void *arg))
功能:創建一個線程。

返回值:成功創建返回值爲0,錯誤返回錯誤號。注意:由於創建線程函數是一個庫函數,不是系統調用函數。所以其錯誤信息不能用perror()進行打印,採用strerror(錯誤號)可以將錯誤信息打印出來。其中strerror函數是包含#include<string.h>之中的一個庫函數。

參數: 
參數1:是一個傳出參數,用於保存成功創建線程之後對應的線程id。 
參數2:表示線程的屬性,通常默認傳NULL,如果想使用具體的屬性也可以修改具體的參數。 
參數3:函數指針,一個指向函數的指針。指向創建線程所執行函數的入口地址,函數執行完畢,則線程結束。 
參數4:線程主函數執行期間所使用的參數。 
3.獲取線程id 
函數原型:

pthred_t pthread_self(void);

功能:獲取當前線程的id。 
參數:無參。

4.單個線程退出

函數原型: void pthread_exit(void *retval) 
參數:retval表示線程的退出狀態,通常穿NULL。當要求傳出具體的退出狀態時,可以使用retval。

當使用exit函數退出線程時,存在的問題是如果當前還有線程沒有執行相應的任務,但是由於進程的退出,強制使得線程被迫退出。因爲線程依賴與進程這是非常危險的退出方式,因此提出來了單線程的退出。不會影響到其他線程的撤銷以及進程的撤銷。
5.阻塞等待線程退出,回收線程的資源。 
函數原型:int pthread_join(pthread_t thread, void **retval)

參數: pthread爲線程id,retval爲線程的狀態。可以與pthread_exit()結合使用。

調用該函數的線程將掛起等待,爲阻塞的狀態。直到id爲thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下: 
1.如果thread線程通過return返回,retval所指向的單元裏存放的是thread線程函數的返回值。 
2.如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元裏存放的是常數PTHREAD_CANCELED。 
3.如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數。 
4.如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。 
6.線程資源正確釋放

每個進程創建以後都應該調用pthread_join 或 pthread_detach 函數,只有這樣在線程結束的時候資源(線程的描述信息和stack)才能被釋放.

如果在新線程裏面沒有調用pthread_join 或 pthread_detach會導致內存泄漏, 如果你創建的線程越多,你的內存利用率就會越高, 直到你再無法創建線程,最終只能結束進程。

解決方法有三個:

1.   線程裏面調用 pthread_detach(pthread_self()) 這個方法最簡單

2. 在創建線程的設置PTHREAD_CREATE_DETACHED屬性

3. 創建線程後用 pthread_join() 一直等待子線程結束。

一般情況下,線程終止後,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態爲止。但是線程也可以被置爲detach狀態,這樣的線程一旦終止就立刻回收它佔用的所有資源,而不保留終止狀態。不能對一個已經處於detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL。如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。
通常情況下,若創建一個線程不關心它的返回值,也不想使用pthread_join來回收(調用pthread_join的進程會阻塞),就可以使用pthread_detach,將該線程的狀態設置爲分離態,使線程結束後,立即被系統回收。

7.取消線程與取消點

取消操作允許線程請求終止其所在進程中的任何其他線程。不希望或不需要對一組相關的線程執行進一步操作時,可以選擇執行取消操作。

取消線程的一個示例是異步生成取消條件,例如,用戶請求關閉或退出正在運行的應用程序。另一個示例是完成由許多線程執行的任務。其中的某個線程可能最終完成了該任務,而其他線程還在繼續運行。由於正在運行的線程此時沒有任何用處,因此應當取消這些線程。

取消點:

僅當取消操作安全時才應取消線程。pthreads 標準指定了幾個取消點,其中包括:

  • 通過 pthread_testcancel 調用以編程方式建立線程取消點。

  • 線程等待 pthread_cond_wait 或 pthread_cond_timedwait(3C) 中的特定條件出現。

  • 被 sigwait(2) 阻塞的線程。

  • 一些標準的庫調用。通常,這些調用包括線程可基於其阻塞的函數。有關列表,請參見 cancellation(5) 手冊頁。

缺省情況下將啓用取消功能。有時,您可能希望應用程序禁用取消功能。如果禁用取消功能,則會導致延遲所有的取消請求,直到再次啓用取消請求。有關禁用取消功能的信息,請參見pthread_setcancelstate 語法

取消模式:

pthread支持三種取消模式,模式爲兩位二進制的編碼,稱爲“取消狀態”和“取消類型”。每種模式提供開,關兩種狀態(編碼技術提供四種狀態,最後一種是冗餘的)

取消狀態可以是“啓用(enable)”和“禁用(disable)

取消類型可以是被“推遲”或“異步”。

pthread_setcancelstate 語法

int	pthread_setcancelstate(int state, int *oldstate);
#include <pthread.h>

int oldstate;

int ret;

/* enabled */

ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);


/* disabled */

ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

pthread_setcancelstate 返回值

pthread_setcancelstate() 在成功完成之後返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,pthread_setcancelstate() 函數將失敗並返回相應的值

EINVAL

描述:

狀態不是 PTHREAD_CANCEL_ENABLE 或 PTHREAD_CANCEL_DISABLE。

pthread_setcanceltype 語法

int	pthread_setcanceltype(int type, int *oldtype);
#include <pthread.h>

int oldtype;

int ret;

/* deferred mode */

ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

/* async mode*/

ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);

創建線程時,缺省情況下會將取消類型設置爲延遲模式。在延遲模式下,只能在取消點取消線程。在異步模式下,可以在執行過程中的任意一點取消線程。因此建議不使用異步模式。

pthread_setcanceltype 返回值

pthread_setcanceltype() 在成功完成之後返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗並返回對應的值。

EINVAL

描述:

類型不是 PTHREAD_CANCEL_DEFERRED 或 PTHREAD_CANCEL_ASYNCHRONOUS。

pthread_testcancel 語法

void pthread_testcancel(void);
#include <pthread.h>


pthread_testcancel(); 

當線程取消功能處於啓用狀態且取消類型設置爲延遲模式時,pthread_testcancel() 函數有效。如果在取消功能處於禁用狀態下調用 pthread_testcancel(),則該函數不起作用。

請務必僅在線程取消操作安全的序列中插入 pthread_testcancel()。除通過 pthread_testcancel() 調用以編程方式建立的取消點以外,pthread 標準還指定了幾個取消點。有關更多詳細信息,請參見取消點

pthread_testcancel 返回值

pthread_testcancel() 沒有返回值。

pthread_setcancelstate()函數只是改變本線程(注意是本線程)的cancel state。所以T1進入fun()函數,
執行到pthread_setcancelstate()函數時,只是改變了T1本身的cancel state,並不能改變T2的cancel state。
 
線程執行到pthread_testcancel()函數時,並不一定會馬上取消(退出)。
 
描述一下取消一個線程的過程:
1) 其他線程通過調用pthread_cancel()函數,向目標線程發送取消請求(cancellation request)。
2) 取消請求發出後,根據目標線程的cancel state來決定取消請求是否會到達目標線程:
  a. 如果目標線程的cancel state是PTHREAD_CANCEL_ENABLE(默認),取消請求會到達目標線程。
  b. 如果目標線程的cancel state是PTHREAD_CANCEL_DISABLE,取消請求會被放入隊列。直到目標線程的cancel state變爲PTHREAD_CANCEL_ENABLE,取消請求才會從隊列裏取出,發到目標線程。
3) 取消請求到達目標線程後,根據目標線程的cancel type來決定線程何時取消:
  a. 如果目標線程的cancel type是PTHREAD_CANCEL_DEFERRED(默認),目標線程並不會馬上取消,而是在執行下一條cancellation point的時候纔會取消。有很多系統函數都是cancellation point,、
  詳細的列表可以在Linux上用man 7 pthreads查看。除了列出來的cancellation point,pthread_testcancel()也是一個cancellation point。就是說目標線程執行到pthread_testcancel()函數的時候,
  如果該線程收到過取消請求,而且它的cancel type是PTHREAD_CANCEL_DEFERRED,那麼這個線程就會在這個函數裏取消(退出),這個函數就不再返回了,目標線程也沒有了。
  b. 如果目標線程的cancel type是PTHREAD_CANCEL_ASYNCHRONOUS(也就是異步取消),目標線程會立即取消(這裏的“立即”只是說目標線程不用等執行到屬於cancellation point的函數的時候纔會取消,
  它會在獲得調度之後立即取消,
  因爲內核調度會有延時,所以並不能保證時間上的“立即”)。

8.linux多線程信號處理

在linux下,每個進程都有自己的signal mask,這個信號掩碼指定哪個信號被阻塞,哪個不會被阻塞,通常用調用sigmask來處理。同時每個進程還有自己的signal action,這個行爲集合指定了信號該如何處理,通常調用sigaction來處理。

使用了多線程後,便有些疑問:

信號發生時,哪個線程會收到
是不是每個線程都有自己的mask及action
每個線程能按自己的方式處理信號麼

首先,信號的傳遞是根據情況而定的:


如果是異常產生的信號(比如程序錯誤,像SIGPIPE、SIGEGV這些),則只有產生異常的線程收到並處理。
如果是用pthread_kill產生的內部信號,則只有pthread_kill參數中指定的目標線程收到並處理。
如果是外部使用kill命令產生的信號,通常是SIGINT、SIGHUP等job control信號,則會遍歷所有線程,直到找到一個不阻塞該信號的線程,然後調用它來處理。(一般從主線程找起),注意只有一個線程能收到。

其次,每個線程都有自己獨立的signal mask,但所有線程共享進程的signal action。這意味着,你可以在線程中調用pthread_sigmask(不是sigmask)來決定本線程阻塞哪些信號。但你不能調用sigaction來指定單個線程的信號處理方式。如果在某個線程中調用了sigaction處理某個信號,那麼這個進程中的未阻塞這個信號的線程在收到這個信號都會按同一種方式處理這個信號。另外,注意子線程的mask是會從主線程繼承而來的。

第三個問題,因爲signal action共享的問題,已經知道不能。

pthread & signal 
 
pthread線程和信號


所有的異步信號發到整個進程的所有線程(異步信號如kill, lwp_kill, sigsend, kill等調用產生的都是,異步信號也稱爲中斷),而且所有線程共享信號的處理行爲(即sigaction的設置,對於同一信號的設置,某一線程的更改會影響到所有線程)。但每個線程可以有自己的mask來阻止信號的發送,所以可以通過線程對mask的設置來決定信號發送到哪個線程。設置mask的函數爲:

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)

此外,線程可以通過sleep(超過指定時間或調用的進程/線程捕捉到某個信號並從信號處理程序返回時,sleep返回)或者sigwait來等待一個或多個信號發生。

#include <signal.h>
int pthread_sigwait(const sigset_t *restrict set, int *restrict signop);

給進程發送信號可以調用kill,同樣給線程調用信號可以使用pthread_kill

#include <signal.h>
int pthread_kill(pthread_t thread, int signo);

可以發送一個0的signo來檢查線程是否存在,如果信號的默認行爲是終止進程(例如SIGARLM),那麼把該信號發送給某個線程會殺掉整個進程的所有線程。

另外注意ALARM是進程資源,並且所有線程共享相同的ALARM,設置一個alarm()會發送SIGARLM信號給所有線程,所以他們不可能互補干擾的使用alarm()。

4. 相關函數 

sigaction(查詢或設置信號處理方式)


#include<signal.h>
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);

sigaction()會依參數signum指定的信號編號來設置該信號的處理函數。參數signum可以指定SIGKILL和SIGSTOP以外的所有信號。


如參數結構sigaction定義如下

struct sigaction
{
   void (*sa_handler) (int);
   sigset_t sa_mask;
   int sa_flags;
   void (*sa_restorer) (void);
}

sa_handler此參數和signal()的參數handler相同,代表新的信號處理函數,其他意義請參考signal()。
sa_mask 用來設置在處理該信號時暫時將sa_mask 指定的信號擱置。
sa_restorer 此參數沒有使用。
sa_flags 用來設置信號處理的其他相關操作,下列的數值可用。

 
sigfillset(將所有信號加入此信號集)

#include<signal.h>
int sigfillset(sigset_t * set);
 
sigfillset()用來將參數set信號集初始化,然後把所有的信號加入到此信號集裏。
 
sigemptyset(初始化信號集)  

#include<signal.h>
int sigemptyset(sigset_t *set);
 
sigemptyset()用來將參數set信號集初始化並清空。
 
pthread_sigmask(更改或檢查調用線程的信號掩碼)
 
#include <pthread.h>
#include<signal.h>
int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);
 
how用來確定如何更改信號組,可以爲以下值之一:

    SIG_BLOCK:向當前的信號掩碼中添加new,其中new表示要阻塞的信號組。
    SIG_UNBLOCK:向當前的信號掩碼中刪除new,其中new表示要取消阻塞的信號組。
    SIG_SETMASK:將當前的信號掩碼替換爲new,其中new表示新的信號掩碼。

pthread_kill(向線程發送信號)
 
#include <pthread.h>
#include<signal.h>
int pthread_kill(thread_t tid, int sig);
 
pthread_kill()將信號sig發送到由tid指定的線程。tid所指定的縣城必須與調用線程在同一個進程中。

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