Linux多線程編程小結
1.Linux進程與線程
Linux進程創建一個新線程時,線程將擁有自己的棧(由于線程有自己的局部變量),但與它的創建者共享全局變量、文件描寫敘述符、信號句柄和當前文件夾狀態。
Linux通過fork創建子進程與創建線程之間是有差別的:fork創建出該進程的一份拷貝,這個新進程擁有自己的變量和自己的PID,它的時間調度是獨立的,它的運行差點兒全然獨立于父進程。
進程能夠看成一個資源的基本單位,而線程是程序調度的基本單位,一個進程內部的線程之間共享進程獲得的時間片。
2._REENTRANT宏
在一個多線程程序里,默認情況下,僅僅有一個errno變量供全部的線程共享。在一個線程準備獲取剛才的錯誤代碼時,該變量非常easy被還有一個線程中的函數調用所改變。相似的問題還存在于fputs之類的函數中,這些函數通經常使用一個單獨的全局性區域來緩存輸出數據。
為解決問題,須要使用可重入的例程。可重入代碼能夠被多次調用而仍然工作正常。編寫的多線程程序,通過定義宏_REENTRANT來告訴編譯器我們須要可重入功能,這個宏的定義必須出現于程序中的不論什么#include語句之前。
_REENTRANT為我們做三件事情,而且做的很優雅:
(1)它會對部分函數又一次定義它們的可安全重入的版本號,這些函數名字一般不會發生改變,僅僅是會在函數名后面加入_r字符串,如函數名gethostbyname變成gethostbyname_r。
(2)stdio.h中原來以宏的形式實現的一些函數將變成可安全重入函數。
(3)在error.h中定義的變量error如今將成為一個函數調用,它可以以一種安全的多線程方式來獲取真正的errno的值。
3.線程的基本函數
大多數pthread_XXX系列的函數在失敗時,并未遵循UNIX函數的慣例返回-1,這樣的情況在UNIX函數中屬于一少部分。假設調用成功,則返回值是0,假設失敗則返回錯誤代碼。
1).線程創建:
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
參數說明:
thread:指向pthread_create類型的指針,用于引用新創建的線程。
attr:用于設置線程的屬性,一般不須要特殊的屬性,所以能夠簡單地設置為NULL。
*(*start_routine)(void *):傳遞新線程所要運行的函數地址。
arg:新線程所要運行的函數的參數。
調用假設成功,則返回值是0,假設失敗則返回錯誤代碼。
2).線程終止
#include <pthread.h>
void pthread_exit(void *retval);
參數說明:
retval:返回指針,指向線程向要返回的某個對象。
線程通過調用pthread_exit函數終止運行,并返回一個指向某對象的指針。注意:絕不能用它返回一個指向局部變量的指針,由于線程調用該函數后,這個局部變量就不存在了,這將引起嚴重的程序漏洞。
3).線程同步
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
參數說明:
th:將要等待的張璐,線程通過pthread_create返回的標識符來指定。
thread_return:一個指針,指向還有一個指針,而后者指向線程的返回值。
一個簡單的多線程Demo(thread1.c):
編譯這個程序時,須要定義宏_REENTRANT:
gcc -D_REENTRANT thread1.c -o thread1 –lpthread
執行這個程序:
$ ./thread1輸出:
thread_function is running. Argument was Hello World
Waiting for thread to finish...
Thread joined, it returned Thank you for your CPU time!
Message is now Bye!
這個樣例值得我們去花時間理解,由于它將作為幾個樣例的基礎。
pthread_exit(void *retval)本身返回的就是指向某個對象的指針,因此,pthread_join(pthread_t th, void **thread_return);中的thread_return是二級指針,指向線程返回值的指針。
能夠看到,我們創建的新線程改動的數組message的值,而原先的線程也能夠訪問該數組。假設我們調用的是fork而不是pthread_create,就不會有這種效果了。原因是fork創建子進程之后,子進程會拷貝父進程,兩者分離,相互不干擾,而線程之間則是共享進程的相關資源。
4.線程的同一時候運行
接下來,我們來編寫一個程序,以驗證兩個線程的運行是同一時候進行的。當然,假設是在一個單處理器系統上,線程的同一時候運行就須要靠CPU在線程之間的高速切換來實現了。
我們的程序須要利用一個原理:即除了局部變量外,全部其它的變量在一個進程中的全部線程之間是共享的。
在這個程序中,我們是在兩個線程之間使用輪詢技術,這樣的方式稱為忙等待,所以它的效率會非常低。在本文的興許部分,我們將介紹一種更好的解決的方法。
以下的代碼中,兩個線程會不斷的輪詢推斷flag的值是否滿足各自的要求。
編譯這個程序:
gcc -D_REENTRANT thread2.c -o thread2 –lpthread
執行這個程序:
$ ./thread2
121212121212121212
Waiting for thread to finish...
5.線程的同步
在上述演示樣例中,我們採用輪詢的方式在兩個線程之間不停地切換是很笨拙且沒有效率的實現方式,幸運的是,專門有一級設計好的函數為我們提供更好的控制線程運行和訪問代碼臨界區的方法。
本小節將介紹兩個線程同步的基本方法:信號量和相互排斥量。這兩種方法非常類似,其實,它們能夠互相通過對方來實現。但在實際的應用中,對于一些情況,可能使用信號量或相互排斥量中的一個更符合問題的語義,而且效果更好。
5.1用信號量進行同步
1.信號量創建
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
參數說明:
sem:信號量對象。
pshared:控制信號量的類型,0表示這個信號量是當前進程的局部信號量,否則,這個信號量就能夠在多個進程之間共享。
value:信號量的初始值。
2.信號量控制
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
sem_post的作用是以原子操作的方式給信號量的值加1。
sem_wait的作用是以原子操作的方式給信號量的值減1,但它會等到信號量非0時才會開始減法操作。假設對值為0的信號量調用sem_wait,這個函數就會等待,直到有線程添加了該信號量的值使其不再為0。
3.信號量銷毀
#include <semaphore.h>
int sem_destory(sem_t *sem);
這個函數的作用是,用完信號量后對它進行清理,清理該信號量所擁有的資源。假設你試圖清理的信號量正被一些線程等待,就會收到一個錯誤。
與大多數Linux函數一樣,這些函數在成功時都返回0。
以下編碼實現輸入字符串,統計每行的字符個數,以“end”結束輸入:
編譯這個程序:
gcc -D_REENTRANT thread2.c -o thread2 –lpthread
執行這個程序:
$ ./thread3 Input some text. Enter 'end' to finish 123 You input 3 characters 1234 You input 4 characters 12345 You input 5 characters end Waiting for thread to finish… Thread join |
通過使用信號量,我們堵塞了統計字符個數的線程,這個程序似乎對高速的文本輸入和悠閑的暫停都非常適用,比之前的輪詢解決方式效率上有了本質的提高。 5.2用相互排斥量進行線程同步
還有一種用在多線程程序中同步訪問的方法是使用相互排斥量。它同意程序猿鎖住某個對象,使得每次僅僅能有一個線程訪問它。為了控制對關鍵代碼的訪問,必須在進入這段代碼之前鎖住一個相互排斥量,然后在完畢操作之后解鎖它。
用于相互排斥量的基本函數和用于信號量的函數很類似:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t, *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destory(pthread_mutex_t *mutex);
與其它函數一樣,成功時返回0,失敗時將返回錯誤代碼,但這些函數并不設置errno,所以必須對函數的返回代碼進行檢查。相互排斥量的屬性設置這里不討論,因此設置成NULL。
我們用相互排斥量來重寫剛才的代碼例如以下:
編譯這個程序:
gcc -D_REENTRANT thread4.c -o thread4 –lpthread
執行這個程序:
$ ./thread4 Input some text. Enter 'end' to finish 123 You input 3 characters 1234 You input 4 characters 12345 You input 5 characters end You input 3 characters Thread joined |
6.線程的屬性
之前我們并未談及到線程的屬性,能夠控制的線程屬性是許多的,這里面僅僅列舉一些經常使用的。
如在前面的演示樣例中,我們都使用的pthread_join同步線程,但事實上有些情況下,我們并不須要。如:主線程為服務線程,而第二個線程為數據備份線程,備份工作完畢之后,第二個線程能夠直接終止了,它沒有必要再返回到主線程中。因此,我們能夠創建一個“脫離線程”。
以下介紹幾個經常使用的函數:
(1)int pthread_attr_init (pthread_attr_t* attr);
功能:對線程屬性變量的初始化。
attr:線程屬性。
函數返回值:成功:0,失敗:-1
(2) int pthread_attr_setscope (pthread_attr_t* attr, int scope);
功能:設置線程綁定屬性。
attr:線程屬性。
scope:PTHREAD_SCOPE_SYSTEM(綁定);PTHREAD_SCOPE_PROCESS(非綁定)
函數返回值:成功:0,失敗:-1
(3) int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
功能:設置線程分離屬性。
attr:線程屬性。
detachstate:PTHREAD_CREATE_DETACHED(分離);PTHREAD_CREATE_JOINABLE(非分離)
函數返回值:成功:0,失敗:-1
(4) int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy);
功能:設置創建線程的調度策略。
attr:線程屬性;
policy:線程調度策略:SCHED_FIFO、SCHED_RR和SCHED_OTHER。
函數返回值:成功:0,失敗:-1
(5) int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
功能:設置線程優先級。
attr:線程屬性。
param:線程優先級。
函數返回值:成功:0,失敗:-1
(6) int pthread_attr_destroy (pthread_attr_t* attr);
功能:對線程屬性變量的銷毀。
attr:線程屬性。
函數返回值:成功:0,失敗:-1
(7)其它
int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize);//設置新創建線程棧的保護區大小。
int pthread_attr_setinheritsched(pthread_attr_t* attr, int inheritsched);//決定如何設置新創建線程的調度屬性。
int pthread_attr_setstack(pthread_attr_t* attr, void* stackader,size_t stacksize);//兩者共同決定了線程棧的基地址以及堆棧的最小尺寸(以字節為單位)。
int pthread_attr_setstackaddr(pthread_attr_t* attr, void* stackader);//決定了新創建線程的棧的基地址。
int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);//決定了新創建線程的棧的最小尺寸(以字節為單位)。
例:創建優先級為10的線程。
pthread_attr_t attr; struct sched_param param; pthread_attr_init(&attr); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); //綁定 pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); //分離 pthread_attr_setschedpolicy(&attr, SCHED_RR); param.sched_priority = 10; pthread_attr_setschedparam(&attr, ¶m); pthread_create(xxx, &attr, xxx, xxx); pthread_attr_destroy(&attr); |
以下實現一個脫離線程的程序,創建一個線程,其屬性設置為脫離狀態。子線程結束時,要使用pthread_exit,原來的主線程不再等待與子線程又一次合并。代碼例如以下:
編譯這個程序:
gcc -D_REENTRANT thread5.c -o thread5 –lpthread
執行這個程序:
$ ./thread5
thread_function is running. Argument: hello world!
Waiting for thread to finished...
Waiting for thread to finished...
Waiting for thread to finished...
Waiting for thread to finished...
Second thread setting finished flag, and exiting now
Other thread finished!
通過設置線程的屬性,我們還能夠控制線程的調試,其方式與設置脫離狀態是一樣的。
7.取消一個線程
有時,我們想讓一個線程能夠要求還有一個線程終止,線程有方法做到這一點,與信號處理一樣,線程能夠在被要求終止時改變其行為。
先來看用于請求一個線程終止的函數:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
這個函數簡單易懂,提供一個線程標識符,我們就能夠發送請求來取消它。
線程能夠用pthread_setcancelstate設置自己的取消狀態。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
參數說明:
state:能夠是PTHREAD_CANCEL_ENABLE同意線程接收取消請求,也能夠是PTHREAD_CANCEL_DISABLE忽略取消請求。
oldstate:獲取先前的取消狀態。假設對它沒興趣,能夠簡單地設置為NULL。假設取消請求被接受了,線程能夠進入第二個控制層次,用pthread_setcanceltype設置取消類型。
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
參數說明:
type:能夠取PTHREAD_CANCEL_ASYNCHRONOUS,它將使得在接收到取消請求后馬上採取行動;還有一個是PTHREAD_CANCEL_DEFERRED,它將使得在接收到取消請求后,一直等待直到線程運行了下述函數之中的一個后才採取行動:pthread_join、pthread_cond_wait、pthread_cond_timedwait、pthread_testcancel、sem_wait或sigwait。
oldtype:同意保存先前的狀態,假設不想知道先前的狀態,能夠傳遞NULL。
默認情況下,線程在啟動時的取消狀態為PTHREAD_CANCEL_ENABLE,取消類型是PTHREAD_CANCEL_DEFERRED。
以下編寫代碼thread6.c,主線程向它創建的線程發送一個取消請求。
編譯這個程序:
gcc -D_REENTRANT thread6.c -o thread6 –lpthread
執行這個程序:
$ ./thread6
thread_function is running...
Thread is still running (0)...
Thread is still running (1)...
Thread is still running (2)...
Thread is still running (3)...
Canceling thread...
Waiting for thread to finished...
8.多線程
之前,我們所編寫的代碼里面都不過創建了一個線程,如今我們來演示一下怎樣創建一個多線程的程序。
編譯這個程序:
gcc -D_REENTRANT thread7.c -o thread7 –lpthread
執行這個程序:
$ ./thread7 thread_function is running. Argument was 0 thread_function is running. Argument was 1 thread_function is running. Argument was 2 thread_function is running. Argument was 3 thread_function is running. Argument was 4 Bye from 1 thread_function is running. Argument was 5 Waiting for threads to finished... Bye from 5 Picked up a thread:6 Bye from 0 Bye from 2 Bye from 3 Bye from 4 Picked up a thread:5 Picked up a thread:4 Picked up a thread:3 Picked up a thread:2 Picked up a thread:1 All done |
9.小結
本文主要介紹了Linux環境下的多線程編程,介紹了信號量和相互排斥量、線程屬性控制、線程同步、線程終止、取消線程及多線程并發。
本文比較簡單,僅僅作為初學Linux多線程編程入門之用。
posted on 2014-11-06 10:42 順其自然EVO 閱讀(555) 評論(0) 編輯 收藏 所屬分類: linux